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

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

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월 정식 출시)입니다. 후자는 시나리오 지향으로, 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
  • 앱 등록은 용도별로 나눕니다. ‘전부 다 담은 앱 하나’는 권한이 비대해지고, 사고 발생 시 영향 범위도 넓어집니다.

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월 정식 출시. 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】연결에 사용할 지문(섬프린트)을 기록해 둔다
$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입니다. 기본적으로 한 페이지 분량만 반환되므로, 전체 건수가 필요하다면 -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. 갱신 토큰과 브라우저 세션 쿠키를 실효시킨다
#    주의: 이미 발급된 액세스 토큰은 유효기간까지 사용 가능한 경우가 있다(후술)
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의 효과 범위도 정확히 이해해 둘 필요가 있습니다. 이 명령이 무효화하는 것은 갱신 토큰과 브라우저 세션 쿠키이며, 이미 발급된 액세스 토큰은 그 유효기간이 만료될 때까지 사용 가능한 경우가 있습니다.9 즉시 차단을 원한다면, 계속 액세스 평가(CAE)에 대응하는 애플리케이션·리소스인 것이 전제가 됩니다. ‘revoke했으니 즉시 모든 액세스가 멈춘다’고 생각하지 말고, 중요한 경우에는 계정 비활성화와 병용하여 반영까지의 시간 차를 감안하십시오.

계정을 즉시 삭제하지 말고 먼저 로그인을 막는 것이 실무의 정석입니다. 메일함이나 OneDrive의 인수인계가 끝나기 전에 삭제하면 복구에 수고가 듭니다. 이런 ‘되돌릴 수 없는 조작’은, -WhatIf에 대응하는 자체 함수로 감싸서, 대상 목록을 먼저 출력하여 확인한 뒤 실행하는 형태로 해 두면 안전합니다(“PowerShell의 인수 설계와 모듈화”).

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

논점 선택지 판단 기준
모듈 Graph SDK / Entra PowerShell ID 관리 중심·AzureAD로부터의 이전이라면 Entra, 폭넓은 워크로드라면 Graph SDK2
설치 전부 설치 / 서브모듈만 시작 시간과 의존성을 억제하기 위해 서브모듈 단위로3
인증(대화형) 관리자 계정에 맡김 / 최소 스코프 현황 조사는 .Read. 계열만. 동의는 테넌트에 남는다4
인증(무인) 클라이언트 시크릿 / 인증서 인증서를 권장. 기한 관리와 갱신 절차를 먼저 정한다6
권한 단위 전부 담은 앱 1개 / 용도별 앱 등록 사고 발생 시 영향 범위를 한정할 수 있다
목록 조회 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에 따릅니다.
  • 현황 조사·라이선스 집계·퇴직자 처리 세 가지를 월간으로 돌리기만 해도, 라이선스 낭비와 방치된 계정이라는 흔한 2대 위험을 크게 줄일 수 있습니다.

샘플 코드 다운로드

이 글에서 다룬 코드는 그대로 실행할 수 있는 형태로 묶어 배포하고 있습니다. 연결·휴면 계정 추출·라이선스 집계·퇴직자 처리가 포함되어 있습니다.

샘플 코드 다운로드(zip)

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

# 구문 분석 + 정적 분석(Windows가 아니어도 실행 가능)
./Invoke-SampleTests.ps1

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

관련 글

관련 상담 영역

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

참고 링크

</content>

  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의 명령형과 상호 운용할 수 있는 것, 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이 최소 권한으로 제시되어 있는 것, 관리자 동의가 필요한 것, 무효화 대상이 갱신 토큰과 브라우저 세션 쿠키이며 이미 발급된 액세스 토큰은 유효기간까지 사용될 수 있는 것(즉시 반영에는 계속 액세스 평가가 관련되는 것)에 대해.  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월 정식 출시)입니다.
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 소프트웨어 개발, 기술 상담, 장애 조사를 중심으로 재현이 어려운 장애 조사와 기존 자산이 남아 있는 프로젝트에 강점이 있습니다.

블로그 목록으로 돌아가기