Microsoft Graph PowerShell 입문 ── AzureAD·MSOnline 폐지 이후의 Microsoft 365 운영

· 업데이트: · · PowerShell, Microsoft 365, Microsoft Entra ID, Microsoft Graph, 정보시스템, 자동화, 운영 개선, 보안

수정 이력(5건, 최종 수정 2026년 09월 03일)

이 글에 적용한 변경 사항의 기록입니다. 보관해 둔 수정 전 버전은 DOI가 부여된 고정 URL에서 읽을 수 있습니다.

Codex 리뷰에 따라 상담·문의 링크에 /ko/ 로케일 접두를 붙였습니다. 본문의 기술적인 주장은 바꾸지 않았습니다.
기사 맨 앞에 「이 글의 지식 맵」 절을 추가했습니다. 본문에서 다루는 개념과 그 관계를 요약·그림·상세 페이지 링크로 정리한 것입니다. 본문의 주장은 바꾸지 않았습니다.
외부 리뷰(1283건)에 대응하여 본문을 갱신했습니다. 개별 변경 내용은 아래 이력을 참조하십시오.
무인 실행 설명에서, 인증서를 LocalMachine 저장소에 둔 경우 지문을 전달해도 찾지 못하는 점을 보완했습니다. -CertificateThumbprint는 현재 사용자의 저장소만 검색하므로, LocalMachine일 때는 인증서를 직접 읽어 -Certificate에 전달해야 합니다.
무인 실행(앱 전용 인증) 절차를 Entra 관리 센터의 화면 위치와 인증서 작성·등록까지 포함해 구체화했습니다. 더불어 위임과 앱 전용의 차이를 대비표와 그림으로 정리하고, 용어 목록과 기재 내용이 2026년 7월 시점의 것이라는 주석을 추가했습니다.
최초 공개
이 글을 인용하기(DOI(등록된 아카이브): 10.5281/zenodo.22174987)

아래 DOI는 이전에 등록된 아카이브를 가리키며 현재 본문과 다를 수 있습니다. 현재 본문을 참조할 때는 이 페이지의 URL을 사용하세요.

Go Komura (2026). 「Microsoft Graph PowerShell 입문 ── AzureAD·MSOnline 폐지 이후의 Microsoft 365 운영」. 합동회사 코무라소프트. https://comcomponent.com/ko/blog/powershell-microsoft-graph-introduction/

DOI(등록된 아카이브)
10.5281/zenodo.22174987
DOI(마지막 등록 버전)
10.5281/zenodo.22174988

Microsoft 365 운영을 자동화하는 현장에서 2024년부터 2025년에 걸쳐 가장 큰 변화는 AzureAD 모듈과 MSOnline 모듈의 폐지였습니다. 「퇴직자 처리」, 「라이선스 현황 조사」, 「신입사원 계정 생성」 같은 정형 처리를 Get-MsolUserGet-AzureADUser로 작성해 둔 정보시스템 담당자가 적지 않을 텐데, 이들은 차례로 동작하지 않게 됩니다.

이전 대상은 Microsoft Graph PowerShell SDK입니다. 다만 단순한 명령 이름 치환이 아닙니다. 인증의 사고방식(스코프와 동의), 데이터를 가져오는 방법(OData 필터와 페이징), 무인 실행을 만드는 방법(앱 등록과 인증서)까지 설계의 전제가 바뀝니다. 여기를 이해하지 못한 채 기계적으로 치환하면, 「동작은 하지만 과도한 권한으로 동작한다」, 「건수가 많은 테넌트에서 도중까지만 가져와진다」 같은 다른 문제를 떠안게 됩니다.

이 글에서는 사내 Microsoft 365 운영을 PowerShell로 자동화하는 정보시스템 담당자를 대상으로, 폐지 경위 정리, 연결과 스코프 설계, 무인 실행 구성, 그리고 현황 조사·라이선스 집계·퇴직자 처리라는 정석 3가지 레시피까지를 실무에서 쓸 수 있는 형태로 정리합니다.

1. 먼저 결론

  • MSOnline과 AzureAD는 2024년 3월 30일에 비권장이 되었고, MSOnline은 2025년 5월 30일에 제공이 종료되었으며, AzureAD도 2025년 3월 30일에 지원 종료 후 폐지되었습니다.1
  • 이전 대상은 Microsoft Graph PowerShell SDK, 또는 그 위의 Microsoft Entra PowerShell(2025년 3월에 GA)입니다. 후자는 시나리오 지향이며, AzureAD에서의 이전을 돕는 호환 옵션도 갖습니다.2
  • Microsoft.Graph는 메타모듈입니다. 전부 넣으면 무거우므로, 실무에서는 Microsoft.Graph.Authentication + 사용할 워크로드의 서브모듈만 넣습니다.3
  • 연결은 Connect-MgGraph -Scopes부터 시작합니다. 스코프는 최소 권한으로. 필요한 권한은 Find-MgGraphPermission, 명령이 속한 모듈은 Find-MgGraphCommand로 확인할 수 있습니다.45
  • 무인 실행은 앱 등록+인증서를 이용한 앱 전용 인증입니다. -ClientId -TenantId -CertificateThumbprint로 대화 없이 연결합니다. 클라이언트 시크릿보다 인증서를 권장합니다.46
  • 위임(Delegated)과 앱 전용(Application)에서는 필요한 권한이 별개입니다. 같은 작업이라도 요구하는 스코프가 달라지므로, 무인 실행으로 전환할 때는 권한을 다시 부여해야 합니다.6
  • 목록 취득은 -All, 좁히기는 -Filter, 항목은 -Property. 클라이언트 측 Where-Object로 좁히면 불필요한 취득과 스로틀링을 부릅니다.7
  • 대량 액세스는 조정(스로틀링)됩니다. 429 응답에서는 Retry-After를 따라 기다리는 것이 공식 지침입니다.8
  • 앱 등록은 용도별로 나눕니다. 「전부 넣은 앱 하나」는 권한이 비대해지고, 사고 시 영향 범위도 넓어집니다.

그림의 실선은 항상 성립하는 관계, 점선은 조건이 붙는 관계입니다(성립 조건은 상세 페이지의 관계별 설명에 적혀 있습니다). 관계 전체 목록(총 24건, 근거와 확신도 포함)과 주요 개념의 정의는 지식 맵 상세 페이지에 정리되어 있습니다(일본어). 데이터: JSON-LD / Turtle

2. 폐지 경위와, 지금 무엇을 고를지

먼저 사실관계를 정리합니다. 이 글의 내용은 2026년 7월 시점의 것입니다. 아래 날짜는 모두 Microsoft가 공표한 폐지 일정이며, 이미 경과했습니다.1

모듈 상태
MSOnline(Get-MsolUser 등) 2024년 3월 30일에 비권장. 2025년 5월 30일에 폐지
AzureAD(Get-AzureADUser 등) 2024년 3월 30일에 비권장. 2025년 3월 30일에 지원 종료된 뒤 폐지
Microsoft Graph PowerShell SDK 현행. Graph API를 그대로 cmdlet으로 만든 것
Microsoft Entra PowerShell 2025년 3월에 GA. Graph SDK 위에 구축된 시나리오 지향 모듈2

어느 쪽을 쓸지의 기준은 이렇습니다. Graph API 구조를 그대로 다루고 싶거나, 폭넓은 워크로드(Exchange, Teams, Intune 등)에 손대고 싶다면 Graph PowerShell SDK. Entra ID(구 Azure AD)의 ID 관리가 중심이고 AzureAD 모듈에서의 이전을 되도록 편하게 하고 싶다면 Microsoft Entra PowerShell입니다. 후자는 Graph PowerShell SDK와 상호 운용할 수 있으며, AzureAD 모듈에서의 이전을 지원하는 하위 호환 옵션도 제공됩니다.2

이 글에서는 범용성이 높고 문서도 풍부한 Graph PowerShell SDK를 축으로 설명합니다.

3. 설치 ── 메타모듈을 통째로 넣지 않기

Microsoft.Graph는 다수의 서브모듈을 묶은 메타모듈입니다. 통째로 넣으면 설치와 로드가 모두 무거워지고, 실행 환경에 따라서는 로드만으로 수십 초가 걸리기도 합니다.3

# 【무거움】 모든 워크로드를 넣기
Install-Module Microsoft.Graph -Scope CurrentUser

# 【실무】 인증 + 사용할 워크로드만 넣기
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser   # 필수
Install-Module Microsoft.Graph.Users          -Scope CurrentUser   # 사용자
Install-Module Microsoft.Graph.Groups         -Scope CurrentUser   # 그룹
Install-Module Microsoft.Graph.Identity.DirectoryManagement -Scope CurrentUser  # 라이선스 등
Install-Module Microsoft.Graph.Users.Actions  -Scope CurrentUser   # 사용자에 대한 작업
                                                                   # (Revoke-MgUserSignInSession 등)

# 어떤 명령이 어느 모듈에 있는지, 필요한 권한이 무엇인지를 조사
Find-MgGraphCommand -Command Get-MgUser | Select-Object Module, Permissions -First 1
Find-MgGraphPermission user.read -PermissionType Delegated

PowerShell 7에서의 이용이 권장됩니다. Windows PowerShell 5.1에서도 동작하지만, 성능 면에서도 장래성 면에서도 7을 고를 이유가 큰 영역입니다(「Windows PowerShell 5.1과 PowerShell 7의 차이」).3

4. 연결과 스코프 ── 「일단 ReadWrite.All」을 그만두기

4.1 먼저 용어를 맞춘다

이후 반복해서 나오는 말을 먼저 짧게 정의해 둡니다.

용어 의미
위임(Delegated) 로그인한 사용자 본인으로서 실행하는 형태. 실제로 할 수 있는 일은 「앱에 동의된 스코프」와 「그 사용자가 가진 역할」의 양쪽으로 정해집니다6
앱 전용(Application) 사용자를 거치지 않고 앱 자신으로서 실행하는 형태. 무인 배치는 이쪽. 인증서 또는 클라이언트 시크릿으로 인증합니다6
앱 등록 / 서비스 주체 앱 등록은 앱의 정의. 서비스 주체는 그것을 테넌트에 실체화한 것이며, 권한이나 역할은 여기에 묶입니다
스코프(액세스 권한) User.Read.All 같은 권한 이름. 위임용과 애플리케이션용은 별도 틀이며, 이름이 같아도 다시 부여해야 합니다6
OData Open Data Protocol. Graph 쿼리 구문의 토대이며, -Filter -Property 등은 대응하는 OData 쿼리 옵션으로 변환되어 서버 측에서 처리됩니다7
조정(스로틀링) 요청이 너무 많을 때 서비스 측이 의도적으로 거부하는 메커니즘. HTTP 429와 Retry-After 헤더로 돌아옵니다8
지속적 액세스 평가(CAE) 토큰 유효기간을 기다리지 않고, 해지 등의 이벤트를 리소스 측이 받아 액세스를 끊는 메커니즘. 대응하는 앱·리소스에서만 작동합니다9

이 두 형태의 차이가, 이 글에서 가장 사고로 이어지는 지점입니다. 그림으로 나타내면 다음과 같습니다.

앱 전용 Application ── 무인 실행위임 Delegated ── 대화형 로그인무인화할 때는권한을 다시 부여애플리케이션 권한관리자 동의가 필요인증서로 인증할 수 있는 일 =부여된 권한 그 자체동의한 위임 스코프관리자가 로그인본인이 가진 관리자 역할할 수 있는 일 =스코프와 역할의 교집합

기억법은 하나입니다. 위임=사용자 본인으로서 실행, 앱 전용=무인 배치. 위임에서는 「본인이 할 수 없는 일은 앱도 할 수 없다」, 앱 전용에서는 「앱에 준 만큼, 언제나 할 수 있게 된다」입니다.

4.2 대화형 연결

대화형 연결은 Connect-MgGraph -Scopes입니다. 지정한 스코프에 대해 동의 화면이 나오고, 동의 결과는 테넌트에 기록됩니다.4

# 읽기만 하는 현황 조사 용도. 쓰기 권한을 요구하지 않음
Connect-MgGraph -Scopes 'User.Read.All', 'Organization.Read.All' -NoWelcome

Get-MgContext | Format-List Account, TenantId, Scopes, AuthType   # 현재 연결을 확인
Disconnect-MgGraph

여기가 이전 때 가장 차이가 나는 지점입니다. Get-MsolUser 시절에는 「관리자 계정으로 로그인하면 전부 된다」였지만, Graph에서는 작업마다 필요한 스코프가 정의되어 있으며, 동의한 범위에서만 동작합니다. 이것은 제약이 아니라 안전장치입니다. 현황 조사 스크립트가 잘못 쓰기를 수행하는 사고는, .Read.All만 동의해 두었다면 일어나지 않습니다.

원칙은 세 가지입니다.

  • 읽기 용도에 쓰기 스코프를 요구하지 않는다(User.Read.All로 충분하면 User.ReadWrite.All을 요구하지 않는다)
  • 용도별로 앱 등록을 나눈다(현황 조사용, 계정 생성용, 라이선스 관리용)
  • 동의는 관리자가 의식적으로 한다(한 번 준 동의는 테넌트에 계속 남는다)

필요한 스코프를 모를 때는 Find-MgGraphPermission으로 후보를 찾고, Find-MgGraphCommand로 명령이 요구하는 권한을 확인합니다.5

5. 무인 실행 ── 앱 등록+인증서

작업 스케줄러에서 매일 밤 돌리려면 대화형 로그인을 쓸 수 없습니다. 앱 등록(서비스 주체)과 인증서를 이용한 앱 전용 인증으로 전환합니다.6

절차는 4단계입니다. 실제로 가장 막히는 공정이므로, 화면상의 위치와 명령을 구체적으로 적습니다.6

5.1 앱을 등록한다

Microsoft Entra 관리 센터(https://entra.microsoft.com)에서 다음을 따라갑니다.

ID > 애플리케이션 > 앱 등록 > 새 등록

이름(예: M365-Inventory-Batch)을 입력하고, 지원되는 계정 종류는 「이 조직 디렉터리에만 있는 계정(단일 테넌트)」을 선택한 뒤 「등록」을 누릅니다. 리디렉션 URI는 스크립트에서의 무인 실행만이면 설정할 필요가 없습니다.

등록 후 「개요」 페이지에 표시되는 다음 두 가지를 적어 둡니다. 이것이 연결에 쓰는 값입니다.

개요 페이지의 표시 이름 Connect-MgGraph의 매개변수
애플리케이션 (클라이언트) ID -ClientId
디렉터리 (테넌트) ID -TenantId

참고로 Entra 관리 센터의 탐색 명칭은 이름이 바뀌는 경우가 있습니다. 왼쪽 메뉴 표기가 바뀌어 있어도, 도착하는 페이지 이름은 「앱 등록」입니다. 이것을 기준으로 찾으십시오.

5.2 애플리케이션 권한을 추가하고, 관리자 동의를 부여한다

같은 앱 화면에서 다음을 따라갑니다.

관리 > API 사용 권한 > 사용 권한 추가 > Microsoft Graph > 애플리케이션 권한

여기서 「애플리케이션 권한」을 고르는 것이 핵심입니다. 옆의 「위임된 사용 권한」을 고르면 무인 실행에서는 동작하지 않습니다. 필요한 권한(현황 조사라면 User.Read.All 등)에 체크하고 「사용 권한 추가」로 확정합니다.

이 시점에서는 아직 쓸 수 없습니다. 같은 화면 상단의

「(테넌트 이름)에 관리자 동의를 부여합니다」

버튼을 눌러 확정합니다. 누른 뒤 목록의 「상태」 열이 「(테넌트 이름)에 부여되었습니다」라는 표시로 바뀌면 완료입니다. 여기를 빠뜨리고 「권한은 붙였는데 403으로 떨어진다」가 흔히 막히는 지점입니다.

5.3 인증서를 만들고·업로드하고·둔다

자체 서명 인증서로 충분합니다. 작성은 PowerShell의 New-SelfSignedCertificate로 합니다.

# 【1】 인증서를 만든다(유효기간은 2년. 운영에 맞춰 조정한다)
$cert = New-SelfSignedCertificate `
    -Subject           'CN=M365-Inventory-Batch' `
    -CertStoreLocation 'Cert:\CurrentUser\My' `
    -KeySpec           Signature `
    -KeyExportPolicy   Exportable `
    -KeyAlgorithm      RSA `
    -KeyLength         2048 `
    -HashAlgorithm     SHA256 `
    -NotAfter          (Get-Date).AddYears(2)

# 【2】 연결에 쓸 지문(thumbprint)을 적어 둔다
$cert.Thumbprint

# 【3】 업로드용으로 공개 키만 .cer로 내보낸다(비밀 키는 포함되지 않음)
Export-Certificate -Cert $cert -FilePath 'C:\temp\M365-Inventory-Batch.cer'

내보낸 .cer를 Entra 관리 센터의 앱 화면에서

관리 > 인증서 및 암호 > 인증서 탭 > 인증서 업로드

에서 업로드합니다. 업로드하는 것은 .cer(공개 키)만입니다. 비밀 키를 포함한 .pfx를 올려서는 안 됩니다.

비밀 키를 어디에 둘지는 작업 스케줄러의 실행 계정에 맞춥니다.

실행 계정 인증서 저장소 연결 시 전달 방법 보충
특정 사용자/서비스 계정 Cert:\CurrentUser\My(그 계정으로 작성·가져오기) -CertificateThumbprint 작성한 계정 이외에서는 보이지 않음
SYSTEM, 또는 여러 계정에서 사용 Cert:\LocalMachine\My -Certificate(직접 읽어 전달) 작성에는 관리자 권한이 필요. 실행 계정에 비밀 키 읽기 권한을 부여

여기서 놓치기 쉬운 것이, -CertificateThumbprint-CertificateSubjectName은 현재 사용자의 인증서 저장소만 검색한다는 점입니다.4 인증서를 Cert:\LocalMachine\My에 둔 경우, 지문을 전달해도 찾지 못합니다. 로컬 컴퓨터 저장소를 쓴다면 직접 읽어 -Certificate에 전달합니다.

# LocalMachine에 둔 경우는 이쪽
$cert = Get-ChildItem -Path 'Cert:\LocalMachine\My\A1B2C3D4E5F6...'
Connect-MgGraph -ClientId $clientId -TenantId $tenantId -Certificate $cert -NoWelcome

「직접 실행하면 되는데 작업 스케줄러에서만 인증서를 찾지 못한다」는 문제는, 거의 이 위치와 전달 방법의 어긋남입니다.

5.4 스크립트에서 연결한다

# 무인 실행용 연결(대화 없음)
$connect = @{
    ClientId              = '11111111-2222-3333-4444-555555555555'
    TenantId              = '66666666-7777-8888-9999-000000000000'
    CertificateThumbprint = 'A1B2C3D4E5F6...'    # 실행 계정의 CurrentUser 저장소에 있는 인증서
    NoWelcome             = $true
}
Connect-MgGraph @connect

# 연결 형태 확인: AuthType이 AppOnly이면 앱 전용으로 연결된 것
Get-MgContext | Format-List AppName, ClientId, TenantId, AuthType, Scopes

try {
    # 업무 처리
}
finally {
    Disconnect-MgGraph
}

처음에는 실제로 작업을 등록하기 전에 작업 스케줄러의 실행 계정으로 위 스크립트를 돌려 확인하십시오. 작업 스케줄러 측의 함정(실행 계정, 「사용자가 로그온했는지 여부에 관계없이 실행」, 작업 디렉터리 등)은 「작업 스케줄러의 작업이 실행되지 않음·0x1로 끝남」에 정리해 두었습니다.

5.5 주의점

주의점이 두 가지 있습니다.

(1) 위임과 앱 전용에서는 필요한 권한이 별개입니다. 대화형으로 -Scopes 'User.Read.All'로 동작하던 스크립트를 무인화할 때는, 앱 등록 쪽에 애플리케이션 권한으로서 같은 종류의 권한을 다시 부여하고 관리자 동의가 필요합니다.6

(2) 인증서에는 기한이 있습니다. 만료 당일에 야간 배치가 전멸하는 일은 실제로 자주 있는 사고입니다. 유효기간을 캘린더에 등록하고 갱신 절차를 문서화하십시오. 자격 증명 보관에 대해서는 「PowerShell에서 자격 증명을 안전하게 다루기」도 함께 참조하십시오. 클라이언트 시크릿으로도 연결은 가능하지만, 평문으로 들고 다니는 위험과 기한 관리의 번거로움 때문에 인증서를 권장합니다.

6. 데이터를 가져오는 방법 ── -All / -Filter / -Property

Graph는 페이징을 전제로 한 API입니다. 기본에서는 1페이지분만 돌아오므로, 전체가 필요하면 -All을 붙입니다.7

# 【NG】 페이징을 고려하지 않고 클라이언트 측에서 좁힘(느림·과도한 취득·스로틀링의 원인)
Get-MgUser | Where-Object { $_.Department -eq '영업부' }

# 【OK】 서버 측에서 좁히고, 필요한 항목만, 모든 페이지를 가져옴
Get-MgUser -All -Filter "department eq '영업부'" `
           -Property Id, DisplayName, UserPrincipalName, AccountEnabled, Department |
    Select-Object DisplayName, UserPrincipalName, AccountEnabled

포인트는 세 가지입니다.

  • -Filter는 OData 식이며 서버 측에서 좁힙니다. Where-Object는 로컬에서 좁히므로, 전체를 가져온 뒤 버리게 됩니다
  • -Property로 열을 좁히면 응답이 가벼워집니다. 기본에서는 돌아오지 않는 속성도, 명시하면 돌아오는 것이 있습니다
  • -Property로 가져온 항목은 Select-Object에도 적습니다. 취득과 표시는 별개이므로, 한쪽만이면 빈칸이 됩니다

startsWithendsWith 같은 고급 쿼리, 혹은 건수만 필요할 때는 -ConsistencyLevel eventual-CountVariable을 함께 씁니다.7

# 활성 사용자 수만 센다(전체를 가져오지 않고 카운트를 얻음)
Get-MgUser -Filter 'accountEnabled eq true' -ConsistencyLevel eventual -CountVariable total -Top 1 | Out-Null
"활성 사용자: $total 건"

-Top 1 | Out-Null은 익숙하지 않은 관용이므로 보충합니다. -CountVariable에 들어가는 것은 조건에 맞는 전체 건수이며, -Top 값에는 좌우되지 않습니다. -Top이 정하는 것은 한 번의 응답으로 돌아오는 객체 수뿐입니다. 즉 -Top 1은 「1건만 센다」는 지정이 아니라, 카운트를 얻기 위해 한 번은 던질 수밖에 없는 요청에서, 돌아오는 실제 데이터를 최소로 하기 위한 지정입니다. 생략하면 기본 페이지분의 사용자 객체가 돌아오고, 그것을 Out-Null로 버리게 됩니다. 참고로 -ConsistencyLevel eventual-CountVariable이나 startsWith 같은 고급 쿼리를 쓰기 위해 필수인 지정입니다.7

대량 액세스를 하면 조정(스로틀링)되어 HTTP 429가 돌아옵니다. 공식 지침은 「Retry-After 헤더의 초수에 따라 기다린 뒤 재시도한다」는 것입니다.8 SDK의 cmdlet은 어느 정도의 재시도를 내부에서 수행하지만, 수천 건 규모의 루프를 돌릴 때는 애초에 취득 횟수를 줄이는 것(-Filter-Property로 좁히고, 한 번에 필요한 정보를 가져오기)이 더 확실합니다. 재시도 설계 자체는 「PowerShell의 오류 처리와 재실행 설계」를 참조하십시오.

7. 정석 레시피 3가지

(1) 사용자 현황 조사를 CSV로 내기

# SignInActivity(마지막 로그인 일시)는 User.Read.All만으로는 가져올 수 없고,
# AuditLog.Read.All이 별도로 필요. 가져오지 않는다면 -Property에서 뺄 것
Connect-MgGraph -Scopes 'User.Read.All', 'AuditLog.Read.All' -NoWelcome

$users = Get-MgUser -All -Property Id, DisplayName, UserPrincipalName, AccountEnabled,
                              Department, JobTitle, CreatedDateTime, SignInActivity |
    Select-Object DisplayName, UserPrincipalName, Department, JobTitle, AccountEnabled,
                  @{ n = '작성일'; e = { $_.CreatedDateTime } },
                  # 휴면 판정에는 「성공한 로그인」을 쓴다. LastSignInDateTime은
                  # 대화형 로그인의 「시도」이며, 실패도 포함하고 비대화형은 포함하지 않음
                  @{ n = '마지막 로그인 성공'; e = { $_.SignInActivity.LastSuccessfulSignInDateTime } },
                  @{ n = '마지막 대화형 로그인 시도'; e = { $_.SignInActivity.LastSignInDateTime } },
                  @{ n = '마지막 비대화형 로그인'; e = { $_.SignInActivity.LastNonInteractiveSignInDateTime } }

# 한국어를 포함한 CSV는 UTF-8(BOM 포함)로 하면 Excel에서 열어도 깨지지 않음.
# 인코딩 이름은 버전마다 다름: 7 이후는 utf8BOM, 5.1은 UTF8(BOM 포함)
$enc = if ($PSVersionTable.PSVersion.Major -ge 6) { 'utf8BOM' } else { 'UTF8' }
$users | Export-Csv -Path "D:\현황조사\users_$(Get-Date -f yyyyMMdd).csv" -Encoding $enc -NoTypeInformation

출력되는 CSV의 열 구성은, Select-Object에 적은 순서와 이름이 그대로 헤더 행이 됩니다. Export-Csv는 기본으로 모든 항목을 따옴표로 감싸므로, 1행째는 다음 형태입니다.

"DisplayName","UserPrincipalName","Department","JobTitle","AccountEnabled","작성일","마지막 로그인 성공","마지막 대화형 로그인 시도","마지막 비대화형 로그인"

2행째 이후에는 같은 순서로 사용자별 값이 나열됩니다. 기대한 열이 빈칸으로 늘어선 경우는, -PropertySelect-Object 어느 쪽에 빠뜨렸거나 권한이 부족한 것입니다. 특히 마지막 로그인 성공 이후 3열이 모두 비어 있으면, 다음에 말하는 AuditLog.Read.All 부족을 의심하십시오.

SignInActivity는 휴면 계정을 가려내는 데 유효합니다. 다만 어느 필드를 볼지가 중요합니다. LastSignInDateTime은 대화형 로그인의 시도(실패 포함)를 기록하며, 앱이나 서비스에 의한 비대화형 로그인은 포함하지 않습니다. 이것만으로 판정하면, 공격자의 로그인 실패로 「쓰이고 있다」처럼 보이거나, 실제로 동작 중인 서비스 계정이 「휴면」으로 보이기도 합니다. 휴면 판정에는 성공한 대화형·비대화형 로그인을 반영하는 LastSuccessfulSignInDateTime을 쓰십시오.10 다만 이 속성만은 User.Read.All로는 가져올 수 없고, AuditLog.Read.All이 추가로 필요합니다(더해 테넌트 측 라이선스 요건도 있습니다). 권한이 부족하면 오류가 나거나 값이 비므로, 쓰지 않는다면 -Property에서 빼십시오. 필요한 권한은 Find-MgGraphPermission으로 확인할 수 있습니다.10 CSV 문자 코드 처리는 「PowerShell로 Excel·CSV 업무 처리를 자동화하기」에 정리해 두었습니다.

(2) 라이선스 소비 상황을 집계한다

Connect-MgGraph -Scopes 'Organization.Read.All' -NoWelcome

Get-MgSubscribedSku | Select-Object `
    SkuPartNumber,
    @{ n = '구입 수';   e = { $_.PrepaidUnits.Enabled } },
    @{ n = '할당됨'; e = { $_.ConsumedUnits } },
    @{ n = '여유';     e = { $_.PrepaidUnits.Enabled - $_.ConsumedUnits } } |
    Sort-Object 여유

「남는 라이선스가 있는데 추가 구입하고 있었다」, 「퇴직자의 라이선스가 해제되지 않았다」는 월차로 돌리기만 해도 막을 수 있습니다.

(3) 퇴직자 처리(로그인 차단과 세션 해지)

다음 코드는 「위임(대화형 로그인)」에서의 실행 예입니다. -Scopes를 지정하고 있는 것이 그 표시이며, 실행하면 브라우저에서의 로그인이 요구됩니다. 무인 실행으로 만들려면 5장대로 앱 전용으로 바꿔 조립해야 합니다. 둘의 차이를 먼저 정리합니다.

  위임(아래 코드 예) 앱 전용(무인 실행)
누구로서 동작하는가 로그인한 담당자 본인 앱(서비스 주체) 자신
Connect-MgGraph의 지정 -ClientId + -Scopes -ClientId + -TenantId + -CertificateThumbprint
인증서 불필요 필수(또는 클라이언트 시크릿)
권한의 종류 위임된 사용 권한 애플리케이션 권한(관리자 동의 필요)6
추가로 필요한 것 로그인한 본인의 Entra 관리자 역할(후술)11 대상이 관리자라면 앱 자체에 대한 상위 역할 할당11
맞는 장면 그 자리의 개별 대응, -WhatIf로 사전 확인 야간 배치, 인사 시스템 연동
# ── 위임(대화형 로그인)에서의 실행 예 ──
# 실행하면 로그인 화면이 나온다. 인증서는 불필요
#
# 쓰기를 수반하므로, 전용 앱 등록·전용 스코프로 실행한다.
# -ClientId를 생략하면 SDK 기본의 공유 앱으로 연결되고, 동의한 권한이
# 그 앱(조직에서 공유)에 쌓인다. 퇴직자 처리처럼
# 파괴적인 작업이야말로, 전용 앱 등록에 권한을 격리한다
#
# 작업마다 필요한 권한이 다르므로, 양쪽 작업분을 한꺼번에 요구한다
#   accountEnabled의 변경 → User.EnableDisableAccount.All(최소 권한)
#   로그인 세션의 해지 → User.RevokeSessions.All(최소 권한)
# 모두 User.ReadWrite.All로도 실행할 수 있지만, 권한이 넓어진다
$connect = @{
    ClientId  = '99999999-aaaa-bbbb-cccc-dddddddddddd'   # 퇴직자 처리 전용 앱 등록
    TenantId  = '66666666-7777-8888-9999-000000000000'
    Scopes    = 'User.Read.All', 'User.EnableDisableAccount.All', 'User.RevokeSessions.All'
    NoWelcome = $true
}
Connect-MgGraph @connect

# Revoke-MgUserSignInSession은 Microsoft.Graph.Users.Actions가 필요
Import-Module Microsoft.Graph.Users.Actions

$upn = 'taro.yamada@example.co.jp'
$user = Get-MgUser -UserId $upn -Property Id, DisplayName, AccountEnabled

# 1. 로그인을 차단(삭제는 유예 기간을 둔 뒤에)
Update-MgUser -UserId $user.Id -AccountEnabled:$false

# 2. 새로 고침 토큰과 브라우저의 세션 Cookie를 해지한다
#    주의: 이미 발급된 액세스 토큰은 유효기간까지 쓸 수 있는 경우가 있다(후술)
Revoke-MgUserSignInSession -UserId $user.Id

Write-Host "$($user.DisplayName)의 로그인을 중지했습니다"

여기서 잡아 둘 점이 세 가지 있습니다. 하나는 연결처 앱 등록입니다. -ClientId를 생략하면 Microsoft Graph PowerShell SDK의 기본 앱으로 연결되고, 동의한 권한은 조직에서 공유되는 그 앱에 기록됩니다. 「용도별로 앱 등록을 나눈다」는 4장의 원칙을 실현하려면, -ClientId로 자사의 앱 등록을 명시해야 합니다.4

또 하나는 스코프입니다. Graph에서는 작업마다 필요한 권한이 정의되어 있으며, 「사용자를 쓸 수 있으니 무엇이든 된다」가 아닙니다. accountEnabled 변경과 로그인 세션 해지는 각각 전용 최소 권한을 가지므로, 한쪽만 동의하고 실행하면 연결에는 성공해도 도중 명령에서 권한 부족 오류가 됩니다. 각 작업에 필요한 권한은 Find-MgGraphPermission과, 대응하는 Graph API 레퍼런스의 권한 표에서 확인하십시오.119

그리고 세 번째로, 위임 실행에서는 스코프만으로는 부족합니다. 위와 같이 이용자 본인으로서 로그인해 실행하는 경우, accountEnabled 변경에는 Microsoft Entra의 관리자 역할도 필요합니다. 위임 권한은 「앱이 그 사용자 대신 무엇을 할 수 있는지」만 정할 뿐, 로그인한 사용자 자신의 권한을 끌어올리지는 않기 때문입니다. 권한에 동의했어도 역할이 없으면 실행 시 403으로 실패합니다. 문서는 테넌트 내 모든 관리자에 대해 이 속성을 업데이트할 수 있는 최소 역할을 Privileged Authentication Administrator로 두고, 일반적으로는 「대상보다 상위 관리자 역할이 필요」하다고 정합니다.11 퇴직자 처리 대상이 일반 이용자인지 관리자인지에 따라 필요한 역할이 달라지므로, 운영 담당 계정에 무엇을 할당할지는 사전에 정해 두십시오. 인증서에 의한 앱 전용 실행에서도, 대상이 관리자인 경우는 앱 자체에 대한 상위 역할 할당이 필요합니다.11

이 처리를 야간 배치로 만든다면, 바꾸는 것은 연결 부분뿐입니다.

# ── 앱 전용(무인 실행)으로 바꿔 조립하는 경우 ──
# 사전 준비: 앱 등록의 「API 사용 권한 > 애플리케이션 권한」에
#   User.Read.All / User.EnableDisableAccount.All / User.RevokeSessions.All
# 을 추가하고, 관리자 동의를 부여해 둔다(5.2). 인증서는 5.3에서 만든 것을 쓴다
$connect = @{
    ClientId              = '99999999-aaaa-bbbb-cccc-dddddddddddd'
    TenantId              = '66666666-7777-8888-9999-000000000000'
    CertificateThumbprint = 'A1B2C3D4E5F6...'   # -Scopes 는 지정하지 않음
    NoWelcome             = $true
}
Connect-MgGraph @connect

# 이후(Import-Module 〜 Revoke-MgUserSignInSession)는 위임 버전과 완전히 같음

Revoke-MgUserSignInSession의 효과 범위도 정확하게 이해해 둘 필요가 있습니다. 이 명령이 무효화하는 것은 새로 고침 토큰과 브라우저의 세션 Cookie이며, 이미 발급된 액세스 토큰은 그 유효기간이 끝날 때까지 쓸 수 있는 경우가 있습니다.9 즉시 차단을 구한다면, 지속적 액세스 평가(CAE)에 대응하는 애플리케이션·리소스인 것이 전제가 됩니다. 「revoke했으니 즉시 모든 액세스가 멈춘다」고 생각하지 말고, 중요한 케이스에서는 계정 비활성화와 병용하고, 반영까지의 시차를 감안하십시오.

계정의 즉시 삭제는 피하고, 먼저 로그인을 멈추는 것이 실무의 정석입니다. 사서함이나 OneDrive 인계가 끝나기 전에 삭제하면 복구에 손이 갑니다. 이런 「되돌릴 수 없는 작업」은 -WhatIf에 대응하는 자체 함수로 감싸고, 대상 목록을 먼저 출력해 확인한 뒤 실행하는 형태로 두면 안전합니다(「PowerShell의 인자 설계와 모듈화」).

8. 실무의 정석(판단표)

논점 선택지 판단의 기준
모듈 Graph SDK / Entra PowerShell ID 관리 중심·AzureAD에서의 이전이면 Entra, 폭넓은 워크로드면 Graph SDK2
설치 전부 넣기 / 서브모듈만 시작 시간과 의존을 줄이기 위해 서브모듈 단위로3
인증(대화) 관리자 계정에 맡김 / 최소 스코프 현황 조사는 .Read. 계열만. 동의는 테넌트에 남음4
인증(무인) 클라이언트 시크릿 / 인증서 인증서를 권장. 기한 관리와 갱신 절차를 먼저 정함6
권한의 세분화 전부 넣은 앱 하나 / 용도별 앱 등록 사고 시 영향 범위를 한정할 수 있음
목록 취득 Where-Object / -Filter + -All + -Property 서버 측에서 좁힘. 취득량이 곧 속도와 안정성7
429 대책 즉시 재시도 / Retry-After를 따름 공식 지침. 애초에 취득 횟수를 줄이는 것이 먼저8
위험한 작업 직접 실행 / -WhatIf + 대상 목록의 사전 확인 퇴직자 처리·일괄 삭제는 반드시 대상을 눈으로 확인할 수 있는 형태로

9. 정리

  • MSOnline과 AzureAD는 이미 폐지되었습니다. 이전 대상은 Microsoft Graph PowerShell SDK, 또는 그 위의 Microsoft Entra PowerShell입니다.
  • Microsoft.Graph는 메타모듈이므로, 실무에서는 인증+사용할 워크로드의 서브모듈만 넣습니다.
  • 연결은 스코프 최소화가 원칙입니다. 현황 조사 용도에 쓰기 권한을 요구하지 말고, 용도별로 앱 등록을 나눕니다.
  • 무인 실행은 앱 등록+인증서. 위임과 앱 전용에서는 필요한 권한이 별개라는 점, 인증서에 기한이 있다는 점이 사고의 원인입니다.
  • 데이터 취득은 -All(페이징), -Filter(서버 측 좁히기), -Property(항목 한정)의 3점 세트. 429는 Retry-After를 따릅니다.
  • 현황 조사·라이선스 집계·퇴직자 처리 셋을 월차로 돌리기만 해도, 라이선스 낭비와 방치 계정이라는 흔한 두 가지 큰 위험은 크게 줄일 수 있습니다.

샘플 코드 다운로드

이 글에서 다룬 코드는 그대로 돌릴 수 있는 형태로 묶어 배포합니다. 연결·휴면 계정 추출·라이선스 집계·퇴직자 처리가 들어 있습니다.

샘플 코드 다운로드(zip)

이 글의 샘플은 Windows나 테넌트에 의존하므로 실행 검증은 하지 않았습니다. 구문 분석과 PSScriptAnalyzer에 의한 정적 분석까지는 모든 파일에 대해 실시했지만, 동작은 반드시 자신의 검증 환경에서 확인하십시오.

# 구문 분석 + 정적 분석(Windows 이외에서도 실행할 수 있음)
./Invoke-SampleTests.ps1

설정값(경로, 서버 이름, 테넌트 ID 등)은 예입니다. 그대로 프로덕션 환경에서 실행하지 말고, 자사 환경에 맞춰 바꿔 적용하십시오.

관련 글

관련 상담 영역

합동회사 고무라소프트에서는 Microsoft 365 운영 스크립트의 Graph 이전, 앱 등록과 권한 설계 리뷰, 현황 조사·퇴직자 처리 같은 정형 업무 자동화를 다루고 있습니다.

참고 링크

  1. Microsoft Community Hub(Microsoft Entra Blog), Action required: MSOnline and AzureAD PowerShell retirement - 2025 info and resources. MSOnline과 AzureAD 두 PowerShell 모듈이 2024년 3월 30일에 비권장이 된 것, MSOnline 폐지가 2025년 봄에 실시되어 2025년 5월 30일을 기점으로 제공이 종료되는 것, AzureAD가 2025년 3월 30일에 지원 종료된 뒤 폐지되는 것, 이전 대상이 Microsoft Graph PowerShell SDK 및 Microsoft Entra PowerShell인 것에 대해.  2

  2. Microsoft Learn, What is Microsoft Entra PowerShell?. Microsoft Entra PowerShell이 Microsoft Graph PowerShell SDK 위에 구축된 시나리오 지향 모듈이며 Graph PowerShell SDK의 cmdlet과 상호 운용할 수 있는 것, AzureAD 모듈에서의 이전을 지원하는 하위 호환 옵션을 제공하는 것에 대해. GA(일반 공급) 안내는 Microsoft Entra PowerShell module now generally available(2025년 3월).  2 3 4

  3. Microsoft Learn, Install the Microsoft Graph PowerShell SDK. Microsoft.Graph가 서브모듈 군을 포함하는 메타모듈인 것, 필요한 서브모듈만 개별 설치할 수 있는 것, Microsoft.Graph.Authentication이 인증에 필수인 것, 대응하는 PowerShell 버전에 대해.  2 3 4

  4. Microsoft Learn, Connect-MgGraph. -Scopes에 의한 위임 액세스 권한 요구, -ClientId / -TenantId / -CertificateThumbprint에 의한 앱 전용 인증, -CertificateThumbprint와 -CertificateSubjectName이 현재 사용자 인증서 저장소에서 인증서를 가져오는 것(로컬 컴퓨터 저장소를 쓰는 경우는 직접 읽어 -Certificate에 전달할 것), Get-MgContext에 의한 현재 연결 정보 확인, Disconnect-MgGraph에 의한 절단에 대해.  2 3 4 5 6

  5. Microsoft Learn, Find Microsoft Graph PowerShell commands and permissions. Find-MgGraphCommand에 의한 명령의 소속 모듈·필요 권한·대응 API 검색, Find-MgGraphPermission에 의한 권한 이름 검색에 대해.  2

  6. Microsoft Learn, Use app-only authentication with the Microsoft Graph PowerShell SDK. 앱 등록·애플리케이션 권한·관리자 동의라는 절차, 인증서를 이용한 무인 인증 구성, 위임 액세스 권한과 애플리케이션 액세스 권한의 차이에 대해.  2 3 4 5 6 7 8 9 10

  7. Microsoft Learn, Paging Microsoft Graph data in your app. Microsoft Graph 응답이 페이징되는 것, PowerShell SDK에서 -All을 지정하면 모든 페이지를 가져올 수 있는 것, $filter나 $select에 해당하는 -Filter / -Property에 의한 서버 측 좁히기, 고급 쿼리에서의 -ConsistencyLevel eventual과 건수 취득에 대해.  2 3 4 5 6

  8. Microsoft Learn, Microsoft Graph throttling guidance. 리소스 단위 스로틀링으로 HTTP 429가 돌아오는 것, 응답의 Retry-After 헤더에 지정된 초수를 기다린 뒤 재시도해야 하는 것, 요청 수 자체를 줄이는 설계가 권장되는 것에 대해.  2 3 4

  9. Microsoft Learn, user: revokeSignInSessions (Microsoft Graph API). 로그인 세션 해지에 필요한 액세스 권한으로 User.RevokeSessions.All이 최소 권한에 제시되어 있는 것, 관리자 동의가 필요한 것, 무효화 대상이 새로 고침 토큰과 브라우저의 세션 Cookie이며 이미 발급된 액세스 토큰은 유효기간까지 사용될 수 있는 것(즉시 반영에는 지속적 액세스 평가가 관여하는 것)에 대해.  2 3

  10. Microsoft Learn, signInActivity resource type. 사용자의 signInActivity 속성 취득에 AuditLog.Read.All과 User.Read.All 양쪽 액세스 권한이 필요한 것, 테넌트의 라이선스 요건이 있는 것, lastSignInDateTime이 대화형 로그인 시도(성공·실패 포함)를 나타내는 데 비해 lastSuccessfulSignInDateTime이 성공한 대화형·비대화형 로그인을, lastNonInteractiveSignInDateTime이 비대화형 로그인을 나타내는 것에 대해.  2

  11. Microsoft Learn, Update user (Microsoft Graph API). 사용자 업데이트에 필요한 액세스 권한이 속성 단위로 정의되어 있으며 accountEnabled 변경에는 User.EnableDisableAccount.All이 최소 권한으로 제시되어 있는 것, 더 넓은 User.ReadWrite.All로도 실행할 수 있는 것에 대해. 위임 시나리오에서는 적절한 스코프에 더해 Microsoft Entra 관리자 역할이 필요하며, 테넌트 내 모든 관리자에 대해 accountEnabled를 업데이트할 수 있는 최소 역할이 Privileged Authentication Administrator인 것, 일반적으로는 대상보다 상위 관리자 역할이 필요한 것, 앱 전용 시나리오에서도 대상이 관리자인 경우는 앱에 상위 관리자 역할 할당이 필요한 것에 대해.  2 3 4 5

같은 태그를 공유하는 최신 기사입니다. 더 가까운 주제로 지식을 넓힐 수 있습니다.

이 기사와 가까운 토픽 페이지입니다. 기사를 출발점 삼아 관련 서비스와 다른 기사로 이어집니다.

자주 묻는 질문

이 기사 주제에 대해 상담 시 자주 나오는 질문을 모았습니다.

AzureAD 모듈이나 MSOnline 모듈은 더 이상 쓸 수 없나요?
네, 둘 다 이미 폐지되었습니다. MSOnline과 AzureAD 두 모듈 모두 2024년 3월 30일에 비권장(deprecated)이 되었고, MSOnline은 2025년 5월 30일을 기점으로 제공이 종료되었으며 AzureAD도 2025년 3월 30일에 지원이 종료된 뒤 폐지되었습니다. 아직 동작하는 것처럼 보이는 스크립트가 있더라도, 언제 멈춰도 이상하지 않은 상태입니다. 이전 대상은 Microsoft Graph PowerShell SDK, 또는 그 위에 구축된 Microsoft Entra PowerShell 모듈(2025년 3월에 GA)입니다.
Microsoft.Graph 모듈을 설치하면 무거운데, 가볍게 할 수 있나요?
가능합니다. Microsoft.Graph는 메타모듈이며 아래에 다수의 서브모듈을 가지므로, 전체를 넣으면 설치와 로드 모두 시간이 걸립니다. 실무에서는 사용할 워크로드의 서브모듈만 넣는 것이 현실적입니다. 사용자 관리라면 Microsoft.Graph.Users, 그룹이라면 Microsoft.Graph.Groups, 인증은 필수인 Microsoft.Graph.Authentication과 같은 식입니다. 어떤 명령이 어느 모듈에 속하는지는 Find-MgGraphCommand로 확인할 수 있습니다.
작업 스케줄러에서 무인 실행하고 싶은데, 대화형 로그인이 뜹니다.
앱 등록(서비스 주체)과 인증서를 이용한 앱 전용 인증으로 전환하십시오. Microsoft Entra ID에서 앱을 등록하고 필요한 애플리케이션 권한(Application permissions)에 관리자 동의를 부여한 뒤, Connect-MgGraph에 -ClientId·-TenantId·-CertificateThumbprint를 전달하면 대화 없이 연결할 수 있습니다. 클라이언트 시크릿보다 인증서 쪽이 더 안전하고 유효기간 관리도 명확합니다. 인증서는 실행 계정의 인증서 저장소에 두고, 만료 전에 갱신하는 운영을 반드시 정해 두십시오.
Connect-MgGraph의 -Scopes에는 무엇을 지정하면 되나요?
실행하려는 명령이 요구하는 최소 권한만 지정합니다. 무엇이 필요한지는 Find-MgGraphPermission이나 명령 문서에서 확인할 수 있으며, 읽기만 한다면 User.Read.All처럼 .Read. 계열로 충분합니다. 현황 조사나 감사가 목적이라면 쓰기 권한을 요구하지 마십시오. 한 번 동의한 권한은 테넌트에 기록되므로, 「일단 Directory.ReadWrite.All로 동의」는 장래의 위험이 됩니다. 용도별로 앱 등록을 나누고 권한도 나누는 것이 안전합니다.
Get-MgUser로 건수가 많은 테넌트를 다루면 도중까지만 가져와집니다.
Microsoft Graph의 응답이 페이징되기 때문입니다. -All을 붙이면 모든 페이지를 자동으로 따라가며 가져옵니다. 더불어 대량 취득에서는 조정(스로틀링)을 받아 429 응답이 돌아올 수 있으므로, 필요한 항목만 -Property로 좁히고 필터는 클라이언트 측 Where-Object가 아니라 OData(-Filter)로 서버 측에 맡기는 것이 기본입니다. 건수만 필요하다면 -ConsistencyLevel eventual과 -CountVariable의 조합으로 카운트만 가져올 수 있습니다.

저자 프로필

기사 저자의 프로필 페이지입니다.

Go Komura

합동회사 코무라소프트 대표

Windows 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기