# PowerShell Essentials Cheatsheet for IT Admins This is a plain-text copy of a cheatsheet published by Ellington Technologies, made for pasting into an AI assistant. It's generated from the same source file as the web page. If the two ever differ, the web page is correct: https://jeremiahellington.com/resources/cheatsheets/powershell-essentials - Source (authoritative web version): https://jeremiahellington.com/resources/cheatsheets/powershell-essentials - Author: Jeremiah Ellington - Published: 2026-10-25 - Last updated: 2026-10-28 - Last reviewed: 2026-10-29 - Next scheduled review: 2027-04-30 - Applies to: PowerShell 7.6 LTS and Windows PowerShell 5.1 on Windows 11 and Windows Server 2019+ (noted where they differ). PowerShell 7.4 and 7.5 reach end of support on November 10, 2026 Everyday PowerShell for Windows administration: discovery, files, processes and services, networking, event logs, users, disks, remoting and safe-change habits, plus walk-throughs for Active Directory, Entra ID and Exchange (on-premises and online). --- Most cmdlets work in both **Windows PowerShell 5.1** (built into Windows) and **PowerShell 7** (installed separately, `pwsh`; 7.6 is the current long-term support release). Several network and Windows-management cmdlets come from Windows modules and run only on Windows. > **Tip:** > Before running anything that changes or deletes, add `-WhatIf` to see what it would do, or `-Confirm` to approve each change. ## Windows: three everyday jobs The next three sections each walk through one common Windows job: a service that keeps stopping, a PC that can't reach the file server, and a PC that restarted overnight. Each one starts with a short situation, finds out what's going on with read-only commands, makes the change (labelled, with `-WhatIf` first), confirms it worked, and lists what to try if it didn't. A quick-reference table closes each section. After them come the reference sections, by area, and then the directory and mail jobs. > **Important:** > These three sections were checked against Microsoft Learn, not lab-tested. The people, computers and addresses are made up, and the sample output is illustrative, not captured from a real system. Run the read-only steps first, and make changes only on a computer you're responsible for. ## A service that won't stay running ### The situation: the print spooler stops every morning Contoso's front-desk PC, `PC-FRONT01`, prints the day's visitor badges. For the past week, Grace Kim has found that nothing prints when she arrives, and restarting the PC fixes it until the next morning. The service behind printing is the Print Spooler, `Spooler`. ### Find out what's going on (read-only) *Read-only* ```powershell Get-Service -Name Spooler | Select-Object Name, Status, StartType Get-Service -Name Spooler -DependentServices # services that stop when it stops Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 7031, 7034; StartTime = (Get-Date).AddDays(-7) } | Format-List TimeCreated, Id, ProviderName, Message sc.exe qfailure Spooler # what Windows does when it fails ``` *Sample output (illustrative)* ```text PS> Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 7031, 7034; StartTime = (Get-Date).AddDays(-7) } | Format-List TimeCreated, Id, ProviderName, Message TimeCreated : 10/27/2026 6:42:10 AM Id : 7034 ProviderName : Service Control Manager Message : The Print Spooler service terminated unexpectedly. It has done this 1 time(s). ``` Look for: `Status : Stopped` with `StartType : Automatic` (it's meant to be running); events from **Service Control Manager** at about the same time each day; and, from `sc.exe qfailure`, the `FAILURE_ACTIONS` Windows takes when the service fails. A 7031 message ends with the corrective action, for example "The following corrective action will be taken in 5000 milliseconds: Restart the service." The event says the service stopped, not why. ### Make the change Start the spooler again without restarting the PC. *Changes this computer: restarts the service, -WhatIf first* ```powershell Restart-Service -Name Spooler -WhatIf Restart-Service -Name Spooler ``` `Restart-Service` stops and then starts the service; if it's already stopped, it just starts it. It works only if your account is allowed to control the service, so use an elevated prompt if it's refused. Like `Stop-Service`, it may refuse to stop a service that has running dependent services unless you add `-Force` (Microsoft documents this for `Stop-Service`). ### Confirm it worked *Read-only* ```powershell Get-Service -Name Spooler | Select-Object Name, Status # Running Get-Service -Name Spooler -DependentServices | Select-Object Name, Status ``` Then ask Grace to print a test badge. Run the event query again tomorrow morning: a new 7031 or 7034 means the cause is still there. ### If it didn't work - **It stops again the next morning.** Restarting treats the symptom. Look at what runs at that time (a scheduled job, a print queue that's sent overnight), and check the Application log around the time of the 7031 or 7034 event for an error from the program that crashed: `Get-WinEvent -FilterHashtable @{ LogName = 'Application'; Level = 2; StartTime = (Get-Date).AddDays(-1) }`. - **Something else stopped with it.** If you added `-Force` to restart a service that has running dependent services, `-Force` stops those services too. Check the `-DependentServices` list after the restart and start anything that should be running. - **Windows doesn't restart it on its own.** If `sc.exe qfailure` shows no `FAILURE_ACTIONS`, nothing is set to happen when the service fails. Adding a recovery action changes the service's configuration, so try it on one PC first, and keep looking for the cause. ### Quick reference: services | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Get-Service -Name Spooler` | Shows a service's status | Read-only | | `Get-Service -Name Spooler -DependentServices` | Lists the services that depend on it | Read-only | | `Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 7031, 7034 }` | Finds "terminated unexpectedly" events from Service Control Manager | Read-only | | `sc.exe qfailure Spooler` | Shows the recovery actions for a failed service | Read-only | | `Restart-Service -Name Spooler` | Stops and starts the service (starts it if stopped) | Changes this computer | ## "I can't reach the file server" ### The situation: a share that won't open Monday morning at Contoso. Owen Hart can't open `\\fs01.corp.contoso.com\shared`: Explorer can't find it. Everyone else in the office can open it. Over the weekend, IT moved the file server to new hardware with a new IP address. ### Find out what's going on (read-only) *Read-only* ```powershell Get-NetIPConfiguration # this PC's address, gateway and DNS servers Get-DnsClientServerAddress -AddressFamily IPv4 Resolve-DnsName fs01.corp.contoso.com # the answer this PC is using Resolve-DnsName fs01.corp.contoso.com -Server 192.0.2.53 -DnsOnly # ask the DNS server directly Test-NetConnection fs01.corp.contoso.com -CommonTCPPort SMB # can it reach file sharing? ``` *Sample output (illustrative)* ```text PS> Resolve-DnsName fs01.corp.contoso.com Name Type TTL Section IPAddress ---- ---- --- ------- --------- fs01.corp.contoso.com A 2140 Answer 192.0.2.20 PS> Resolve-DnsName fs01.corp.contoso.com -Server 192.0.2.53 -DnsOnly Name Type TTL Section IPAddress ---- ---- --- ------- --------- fs01.corp.contoso.com A 3600 Answer 192.0.2.45 ``` Look for: DNS servers that are Contoso's own (here `192.0.2.53`); whether the first answer matches the one straight from the DNS server (here it doesn't, so this PC is holding an old answer); and `TcpTestSucceeded` from `Test-NetConnection`. Use your own DNS server's address with `-Server`. ### Make the change Clear this PC's DNS cache so it asks again. It's the same as `ipconfig /flushdns`. *Changes this computer: clears the DNS cache, -WhatIf first* ```powershell Clear-DnsClientCache -WhatIf Clear-DnsClientCache ``` ### Confirm it worked *Read-only* ```powershell Resolve-DnsName fs01.corp.contoso.com # now the new address Test-NetConnection fs01.corp.contoso.com -CommonTCPPort SMB # TcpTestSucceeded : True ``` Then ask Owen to open the share again. ### If it didn't work - **The DNS servers aren't Contoso's.** If `Get-DnsClientServerAddress` lists a public resolver or a home router, internal names like `fs01.corp.contoso.com` won't resolve properly. Fix the adapter's DNS settings (or the DHCP scope that hands them out), not the cache. - **It still gets the old address.** Compare `Resolve-DnsName fs01.corp.contoso.com -NoHostsFile`. If that answer is different, there's an old entry for the server in this PC's hosts file. - **The name is right but `TcpTestSucceeded` is `False`.** DNS is fine and something is blocking the connection: the file server isn't sharing yet, or a firewall on the server or between the two is blocking it. Check from the server's side. ### Quick reference: networking | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Get-NetIPConfiguration` | Shows addresses, gateway and DNS servers per adapter | Read-only | | `Get-DnsClientServerAddress -AddressFamily IPv4` | Lists the DNS servers each adapter uses | Read-only | | `Resolve-DnsName -Server -DnsOnly` | Asks one DNS server, using DNS only | Read-only | | `Resolve-DnsName -NoHostsFile` | Resolves a name without the hosts file | Read-only | | `Test-NetConnection -CommonTCPPort SMB` | Tests a file-sharing connection | Read-only | | `Clear-DnsClientCache` | Clears the DNS client cache (same as `ipconfig /flushdns`) | Changes this computer | ## A PC that restarted overnight ### The situation: open work lost overnight Tuesday, 8:10. Priya Shah in Contoso's sales team left a half-finished quote open on `PC-SALES07` last night. This morning the PC is at the sign-in screen and the quote is gone. Did Windows Update restart it, or did it crash? ### Find out what's going on (read-only) *Read-only* ```powershell (Get-CimInstance Win32_OperatingSystem).LastBootUpTime Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41; StartTime = (Get-Date).AddDays(-2) } | Format-List TimeCreated, Id, ProviderName, Message Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5 ``` *Sample output (illustrative)* ```text TimeCreated : 10/27/2026 3:02:41 AM Id : 1074 ProviderName : User32 Message : The process has initiated the restart of computer PC-SALES07 on behalf of user for the following reason: Operating System: Service pack (Planned) Reason Code: 0x80020010 Shutdown Type: restart ``` Look for: - **1074 from User32:** a planned restart. The message names the process that asked for it, the account and a reason. Microsoft's guidance is that a normal restart shows 1074, and that an update, an installer or a person can be behind it. - **41 from Kernel-Power, or 6008 from EventLog, with no 1074:** an unexpected restart (a crash, a hang or a power loss). - **The source as well as the ID.** Microsoft notes that the same event ID can come from a different source, so check `ProviderName`. ### What to do next This job may not need a change: the answer may be "it was the update, at the time Windows was set to restart". Act on what the events show. - **A 1074 that points to an update.** Windows restarts after an update outside its *active hours*, the hours you expect the PC to be in use. The default is 8 AM to 5 PM, so 3 AM is expected. Ask Priya to save her work before she leaves. If the hours are wrong for her, change them in **Settings** > **Windows Update** > **Advanced options** > **Active hours** (changes this computer). On managed PCs, set them centrally with the Group Policy setting **Turn off auto-restart for updates during active hours** instead. - **A 41 or 6008 with no 1074.** Treat it as a crash, a hang or a power problem, not an update. Look for 1001 (a bug check, from WER-SystemErrorReporting) and 7045 (a service or driver installed) shortly before it. A new driver just before the first crash is a good lead. ### Confirm it worked *Read-only* ```powershell (Get-CimInstance Win32_OperatingSystem).LastBootUpTime # tomorrow: no restart during her working hours ``` After the next update night, run the event query again. A 1074 outside the new active hours is expected; a 41 or 6008 means the problem is still there. ### If it didn't work - **There are no events at all.** Widen `StartTime`, and check that the System log keeps enough history: `Get-WinEvent -ListLog System`. - **It restarted during active hours.** On a managed PC, restart policies may be set centrally, under **Computer Configuration\Administrative Templates\Windows Components\Windows Update** in Group Policy, or through MDM. Microsoft warns that conflicting restart policies can restart a PC when you don't expect it. Ask whoever manages updates. ### Quick reference: restarts | Command | What it does | Read-only or changes something | | --- | --- | --- | | `(Get-CimInstance Win32_OperatingSystem).LastBootUpTime` | Shows when Windows last started | Read-only | | `Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 1074, 6008, 41 }` | Finds planned (1074) and unexpected (41, 6008) restarts | Read-only | | `Get-HotFix` | Lists Windows updates installed through Component-Based Servicing (not every update) | Read-only | | **Settings** > **Windows Update** > **Advanced options** > **Active hours** | Sets the hours Windows won't restart for updates | Changes this computer | ## Discover and get help ```powershell Get-Command *service* # find commands by name Get-Help Get-Service -Examples # examples for a command Update-Help -Scope CurrentUser # download help (7.x; in 5.1, run Update-Help elevated) Get-Service | Get-Member # properties and methods of the output $PSVersionTable # PowerShell version ``` ## Files and text ```powershell Get-ChildItem C:\Logs -Recurse -Filter *.log Get-Content .\app.log -Tail 50 -Wait # follow a log Select-String -Path .\*.log -Pattern "error" -SimpleMatch Copy-Item .\config.json .\config.backup.json Get-ChildItem C:\Temp -Recurse | Measure-Object -Property Length -Sum ``` *Destructive: preview first* ```powershell Remove-Item C:\Temp\old -Recurse -WhatIf # shows what would be deleted Remove-Item C:\Temp\old -Recurse # deletes; no Recycle Bin ``` ## Processes and services ```powershell Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, Id, CPU Get-Service -Name Spooler Restart-Service -Name Spooler Get-Service | Where-Object Status -eq 'Stopped' | Where-Object StartType -eq 'Automatic' Stop-Process -Name notepad -WhatIf ``` ## Networking (Windows) ```powershell Get-NetIPConfiguration # addresses, gateway, DNS per adapter Test-NetConnection example.com -Port 443 # can I reach this service? Resolve-DnsName example.com -Type MX # DNS lookup Get-NetTCPConnection -State Listen | Sort-Object LocalPort Get-DnsClientServerAddress -AddressFamily IPv4 Clear-DnsClientCache ``` ## System information and updates ```powershell Get-ComputerInfo -Property OsName, OsVersion, OsBuildNumber, CsModel (Get-CimInstance Win32_OperatingSystem).LastBootUpTime Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 10 Get-Volume Get-PhysicalDisk | Select-Object FriendlyName, MediaType, HealthStatus, Size ``` ## Event logs ```powershell # Errors in the System log from the last 24 hours Get-WinEvent -FilterHashtable @{ LogName = 'System'; Level = 2; StartTime = (Get-Date).AddDays(-1) } # Unexpected shutdowns (Event ID 6008) Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 6008 } -MaxEvents 10 # Failed sign-ins (Event ID 4625; needs an elevated session) Get-WinEvent -FilterHashtable @{ LogName = 'Security'; Id = 4625 } -MaxEvents 20 ``` ## Local users and groups ```powershell Get-LocalUser | Select-Object Name, Enabled, LastLogon Get-LocalGroupMember -Group Administrators # who has admin rights? Disable-LocalUser -Name 'olduser' -WhatIf ``` > **Note:** > The LocalAccounts module comes with Windows, not with PowerShell 7, but PowerShell 7.6 loads it from the Windows module folder. If these cmdlets don't load in your version, run them in Windows PowerShell 5.1. ## Working with output ```powershell Get-Service | Where-Object Status -eq 'Running' | Select-Object Name, DisplayName Get-Process | Sort-Object WS -Descending | Select-Object -First 5 Get-Service | Export-Csv .\services.csv -NoTypeInformation Get-ComputerInfo | ConvertTo-Json -Depth 2 | Out-File .\info.json Get-ChildItem | Format-Table Name, Length -AutoSize ``` ## Remoting ```powershell Enter-PSSession -ComputerName PC01 # interactive remote shell Invoke-Command -ComputerName PC01, PC02 -ScriptBlock { Get-Service Spooler } Test-WSMan -ComputerName PC01 # is remoting reachable? (errors if WinRM is off) ``` Remoting must be enabled on the target (`Enable-PSRemoting` in an elevated session; it's on by default on Windows Server). Use it only on trusted networks or over a VPN. ## Scripts and modules ```powershell Get-ExecutionPolicy -List Set-ExecutionPolicy RemoteSigned -Scope CurrentUser # allow local scripts for your user Find-Module -Name PSWindowsUpdate # a community module, not from Microsoft Install-Module -Name PSWindowsUpdate -Scope CurrentUser Get-InstalledModule ``` > **Warning:** > Execution policy is a safety guardrail, not a security boundary. Only install modules from sources you trust, and read scripts before running them. ## Directory and mail: four everyday jobs The next four sections each walk through one common job: unlocking an account, adding someone to a group, chasing a stuck email, and handing over a leaver's mailbox. Each one starts with a short situation, finds out what's going on with read-only commands, makes the change (each step labelled, with `-WhatIf` first wherever the cmdlet supports it), confirms it worked, and lists what to try if it didn't. A quick-reference table closes each section. > **Important:** > These four sections were checked against Microsoft Learn, not lab-tested. The people, companies and addresses are made up, and the sample output is illustrative, not captured from a real system. Try changes in a test environment first, and use an admin account that has only the rights the job needs. ## Active Directory (on-premises) You need the `ActiveDirectory` module (part of RSAT) on a domain-joined machine: on Windows Server, install it with `Install-WindowsFeature -Name RSAT-AD-Tools -IncludeAllSubFeature` (elevated); on Windows 11 Pro or Enterprise, add the RSAT Active Directory tools under **Settings > System > Optional features**. ### The situation: a locked-out user Monday, 8:05. Dana Reyes at Contoso calls the help desk: Windows says her account is locked out, and she has a client call at 8:30. Her sign-in name is `dreyes` in the `corp.contoso.com` domain. ### Find out what's going on (read-only) ```powershell Get-ADUser -Identity dreyes # does the account exist, and is it enabled? Search-ADAccount -LockedOut -UsersOnly | Select-Object Name, SamAccountName Get-ADDomainController -Filter * | Select-Object Name, Site # which DCs might hold a stale copy ``` *Sample output (illustrative)* ```text PS> Search-ADAccount -LockedOut -UsersOnly | Select-Object Name, SamAccountName Name SamAccountName ---- -------------- Dana Reyes dreyes ``` Look for: `Enabled : True` from `Get-ADUser` (a disabled account is a different problem, and unlocking won't help), and her name in the locked-out list. ### Make the change Unlock the account, previewing the change first. *Changes the directory: preview with -WhatIf first* ```powershell Unlock-ADAccount -Identity dreyes -WhatIf Unlock-ADAccount -Identity dreyes ``` ### Confirm it worked ```powershell Search-ADAccount -LockedOut -UsersOnly | Select-Object Name, SamAccountName # dreyes should be gone ``` Then ask Dana to sign in while you're still on the call. ### If it didn't work - **She's locked out again within minutes.** Something is still trying her old password: often a phone mail app, a mapped drive or a saved credential on another PC. Ask what she changed recently, and fix that device; unlocking again won't stick until you do. - **One office still sees her as locked.** The unlock is written to one domain controller and then replicates. Check a specific DC with `-Server`, for example `Search-ADAccount -LockedOut -UsersOnly -Server DC02.corp.contoso.com`, and give replication a few minutes. ### Quick reference: Active Directory | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Get-ADUser -Identity dreyes` | Shows one user account (add `-Properties` for more attributes) | Read-only | | `Search-ADAccount -LockedOut -UsersOnly` | Lists locked-out user accounts | Read-only | | `Get-ADDomainController -Filter *` | Lists the domain controllers | Read-only | | `Unlock-ADAccount -Identity dreyes` | Unlocks the account | Changes the directory | ## Entra ID (Microsoft Graph PowerShell) For cloud identity, use the **Microsoft Graph PowerShell** SDK. The older AzureAD and MSOnline modules are deprecated and past their retirement date: Microsoft deprecated them on March 30, 2024 and said they would keep working only through March 30, 2025. If any of your scripts still use them, migrate those scripts to Microsoft Graph PowerShell. *Changes this computer: installs the module* ```powershell Install-Module Microsoft.Graph -Scope CurrentUser -Repository PSGallery ``` ### The situation: a new starter needs a group Sam Ortiz starts in Sales at Contoso today. His account `sam.ortiz@contoso.com` exists, but he can't open the Sales team's files because he isn't in the **Sales** group yet. ### Find out what's going on (read-only) Connect with read-only permissions first. `Connect-MgGraph` opens a sign-in window. The first time you use a scope, it asks you to consent to it, and you consent only to the scopes you name. ```powershell Connect-MgGraph -Scopes 'User.ReadBasic.All', 'GroupMember.Read.All' $user = Get-MgUser -UserId 'sam.ortiz@contoso.com' $group = Get-MgGroup -Filter "displayName eq 'Sales'" $group | Select-Object DisplayName, Id, GroupTypes Get-MgGroupMember -GroupId $group.Id -All | Where-Object Id -eq $user.Id # nothing back = not a member ``` *Sample output (illustrative)* ```text DisplayName Id GroupTypes ----------- -- ---------- Sales 00000000-0000-4000-8000-000000000a01 {} ``` Look for: exactly one group called Sales (if the filter returns more than one, use the right `Id`), and `GroupTypes`. If it includes `DynamicMembership`, membership is set by a rule and you can't add people by hand (see below). ### Make the change Adding a member needs `GroupMember.ReadWrite.All`. Connect again with that scope only when you're ready to change something. *Changes the directory: preview with -WhatIf first* ```powershell Connect-MgGraph -Scopes 'User.ReadBasic.All', 'GroupMember.ReadWrite.All' New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id -WhatIf New-MgGroupMember -GroupId $group.Id -DirectoryObjectId $user.Id ``` ### Confirm it worked ```powershell Get-MgGroupMember -GroupId $group.Id -All | Where-Object Id -eq $user.Id # now returns Sam's Id Disconnect-MgGraph ``` ### If it didn't work - **The group is dynamic.** You can't manually add or remove members of a dynamic membership group. Change the user's attributes (such as department) so the rule picks him up, or ask whoever owns the rule. - **Access denied.** The scope isn't enough on its own: your account also needs an Entra role that's allowed to manage group membership, such as Groups Administrator or User Administrator, or you must be an owner of the group. - **403 Forbidden, and Sales is a distribution list.** Microsoft Graph adds members only to security groups and Microsoft 365 groups. Manage distribution lists and mail-enabled security groups in Exchange Online instead (connect as shown in the Exchange Online section): `Add-DistributionGroupMember -Identity 'Sales' -Member sam.ortiz@contoso.com -WhatIf`, then run it again without `-WhatIf`. ### Quick reference: Entra ID | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Connect-MgGraph -Scopes '...'` | Signs in; the first time, asks you to consent to the named permissions | Signs in (grants consent on first use) | | `Get-MgUser -UserId 'sam.ortiz@contoso.com'` | Gets one user by ID or sign-in name | Read-only | | `Get-MgGroup -Filter "displayName eq 'Sales'"` | Finds a group by name | Read-only | | `Get-MgGroupMember -GroupId $id -All` | Lists a group's direct members | Read-only | | `New-MgGroupMember -GroupId $id -DirectoryObjectId $userId` | Adds a member to a security or Microsoft 365 group | Changes the directory | | `Add-DistributionGroupMember -Identity 'Sales' -Member ...` | Adds a member to a distribution list (Exchange Online) | Changes the directory | | `Disconnect-MgGraph` | Signs out of the session | Read-only (sign-out) | ## Exchange Server (on-premises) Run these in the **Exchange Management Shell** on an Exchange server or on a machine with the Exchange management tools. Exchange Server Subscription Edition (SE) is the supported version: Exchange Server 2016 and 2019 reached end of support on October 14, 2025. ### The situation: an email that never arrived Priya Shah in Contoso's sales team sent a quote to `buyer@example.com` yesterday afternoon. This morning the buyer rings to say it never arrived. Contoso runs its own Exchange server, `EX01`. ### Find out what's going on (read-only) ```powershell Test-ServiceHealth -Server EX01 # are the required Exchange services running? Get-MessageTrackingLog -Server EX01 -Sender priya.shah@contoso.com -Recipients buyer@example.com -Start (Get-Date).AddDays(-2) | Select-Object Timestamp, EventId, Source, MessageSubject Get-Queue -Server EX01 | Where-Object MessageCount -gt 0 | Select-Object Identity, Status, MessageCount, NextHopDomain, LastError ``` *Sample output (illustrative)* ```text PS> Get-Queue -Server EX01 | Where-Object MessageCount -gt 0 | Select-Object Identity, Status, MessageCount, NextHopDomain, LastError Identity Status MessageCount NextHopDomain LastError -------- ------ ------------ ------------- --------- EX01\7 Retry 3 example.com 451 4.4.0 Primary target IP address responded with: "421 4.4.2 Connection dropped." Attempted failover to alternate host, but that did not succeed. ``` Look for: services that aren't running in `Test-ServiceHealth`; in the tracking log, `DEFER` means delivery was delayed and `FAIL` means it failed; and a queue in **Retry** for the recipient's domain, with the reason in `LastError`. ### Make the change The cause here is at the other end: `example.com`'s mail server wasn't accepting connections. Exchange keeps retrying on its own. Once the buyer's IT team says their server is back, you can retry the queue now instead of waiting. *Changes mail flow: preview with -WhatIf first* ```powershell Retry-Queue -Identity 'EX01\7' -WhatIf Retry-Queue -Identity 'EX01\7' ``` ### Confirm it worked ```powershell Get-Queue -Server EX01 | Where-Object NextHopDomain -eq 'example.com' # empty, or no longer listed Get-MessageTrackingLog -Server EX01 -Sender priya.shah@contoso.com -Recipients buyer@example.com -EventId SEND -Start (Get-Date).AddDays(-2) ``` ### If it didn't work - **The queue goes straight back to Retry.** The problem isn't fixed at the other end (or it's DNS, a firewall or a blocklist on yours). Read `LastError` again rather than retrying repeatedly. - **Nothing in the tracking log.** Check the spelling of both addresses and widen `-Start`. In an organization with more than one Exchange server, run the search on each server that handles mail. ### Quick reference: Exchange Server | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Test-ServiceHealth -Server EX01` | Checks that the required Exchange services are running | Read-only | | `Get-MessageTrackingLog -Server EX01 -Sender ... -Recipients ...` | Searches the message tracking log | Read-only | | `Get-Queue -Server EX01` | Lists mail queues, their status and last error | Read-only | | `Retry-Queue -Identity 'EX01\7'` | Forces a new connection attempt for a queue | Changes mail flow | ## Exchange Online (Microsoft 365) ```powershell Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser Connect-ExchangeOnline -UserPrincipalName admin@contoso.com # opens a sign-in window (MFA works) Get-ConnectionInformation # which tenant am I connected to? Get-EXOMailbox -ResultSize 10 | Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeDetails Get-EXOMailbox -RecipientTypeDetails SharedMailbox -ResultSize Unlimited Disconnect-ExchangeOnline -Confirm:$false ``` > **Note:** > ExchangeOnlineManagement 3.10 and later needs PowerShell 7.6 or later; it also works in Windows PowerShell 5.1. Sign in with an admin account that has only the role you need, and disconnect when you're done. ### The situation: a leaver's mailbox Tom Becker left Contoso on Friday. His manager, Ana Silva, needs to keep reading his mailbox for customer emails, and finance wants his Microsoft 365 license back. A common answer: convert his mailbox to a shared mailbox and give Ana Full Access. But first, make sure Tom can't sign in any more. Converting the mailbox doesn't do that: unless you reset the password, his original username and password keep working on the shared mailbox. > **Note:** > **Before you start:** if Tom's account is synced from on-premises Active Directory (`OnPremisesSyncEnabled` is `True` in the check below), disable it in Active Directory, not in Entra: `Disable-ADAccount -Identity tbecker -WhatIf`, then again without `-WhatIf`, and reset its password there too, in place of steps 1 and 3 below. Still revoke his sessions in Entra (step 2). If Contoso runs Exchange hybrid, converting the mailbox works differently; see Microsoft's hybrid notes on the conversion page in the sources. ### Find out what's going on (read-only) The Graph commands need the Microsoft Graph PowerShell module (see the Entra ID section). `User.Read.All` is enough to read whether the account is enabled. The mailbox commands need an Exchange Online connection: if you ran the block above, it ended with a disconnect, so connect again here. ```powershell Connect-MgGraph -Scopes 'User.Read.All' Connect-ExchangeOnline -UserPrincipalName admin@contoso.com $tom = Get-MgUser -UserId 'tom.becker@contoso.com' -Property 'Id,DisplayName,AccountEnabled,OnPremisesSyncEnabled' $tom | Select-Object DisplayName, AccountEnabled, OnPremisesSyncEnabled Get-EXOMailbox -Identity tom.becker@contoso.com | Select-Object DisplayName, PrimarySmtpAddress, RecipientTypeDetails Get-EXOMailboxStatistics -Identity tom.becker@contoso.com | Select-Object DisplayName, ItemCount, TotalItemSize Get-EXOMailboxPermission -Identity tom.becker@contoso.com ``` *Sample output (illustrative)* ```text DisplayName ItemCount TotalItemSize ----------- --------- ------------- Tom Becker 18452 6.214 GB (6,672,315,392 bytes) ``` Look for: `AccountEnabled` (`True` now) and `OnPremisesSyncEnabled` (`True` means the account is synced: see **Before you start**); `RecipientTypeDetails` (`UserMailbox` now); a `TotalItemSize` under 50 GB (a shared mailbox can hold up to 50 GB without a license); and who already has access. ### Make the change Secure Tom's account first (steps 1 to 3), then hand over the mailbox (steps 4 to 6). Keep to this order. Blocking sign-in and resetting the password need an Entra role as well as the scopes: Microsoft lists User Administrator for users who aren't admins, and Privileged Authentication Administrator for admin accounts. **Step 1: block sign-in.** Changes the directory. Connect again with the scopes for steps 1 and 2. *Changes the directory: block sign-in, -WhatIf first* ```powershell Connect-MgGraph -Scopes 'User.Read.All', 'User.EnableDisableAccount.All', 'User.RevokeSessions.All' Update-MgUser -UserId $tom.Id -AccountEnabled:$false -WhatIf Update-MgUser -UserId $tom.Id -AccountEnabled:$false ``` **Step 2: sign him out everywhere.** Changes the directory. This revokes the refresh tokens and browser session cookies Tom already has. *Changes the directory: revoke sessions, -WhatIf first* ```powershell Revoke-MgUserSignInSession -UserId $tom.Id -WhatIf Revoke-MgUserSignInSession -UserId $tom.Id ``` **Step 3: reset the password.** Changes the directory. In the Microsoft 365 admin center, go to **Users** > **Active users**, select Tom, then select **Reset password**. Let it create the password automatically, and don't give it to anyone: nobody needs to sign in as Tom. **Step 4: convert the mailbox to shared.** Changes the mailbox. *Changes the mailbox: convert to shared, -WhatIf first* ```powershell Set-Mailbox -Identity tom.becker@contoso.com -Type Shared -WhatIf Set-Mailbox -Identity tom.becker@contoso.com -Type Shared ``` **Step 5: give Ana Full Access.** Changes the mailbox. *Changes the mailbox: grant Full Access, -WhatIf first* ```powershell Add-MailboxPermission -Identity tom.becker@contoso.com -User ana.silva@contoso.com -AccessRights FullAccess -InheritanceType All -WhatIf Add-MailboxPermission -Identity tom.becker@contoso.com -User ana.silva@contoso.com -AccessRights FullAccess -InheritanceType All ``` **Step 6: remove the license.** Changes the directory. Only after the conversion has finished, and only if the mailbox is under 50 GB: in the Microsoft 365 admin center, go to **Users** > **Active users**, select Tom, select **Licenses and apps**, clear his license and select **Save changes**. Don't delete his account: the shared mailbox needs it. ### Confirm it worked ```powershell Get-MgUser -UserId $tom.Id -Property 'DisplayName,AccountEnabled' | Select-Object DisplayName, AccountEnabled # False Get-EXOMailbox -Identity tom.becker@contoso.com | Select-Object DisplayName, RecipientTypeDetails # SharedMailbox Get-EXOMailboxPermission -Identity tom.becker@contoso.com # Ana listed with FullAccess Disconnect-MgGraph Disconnect-ExchangeOnline -Confirm:$false ``` ### If it didn't work - **Tom can still get in for a while.** Microsoft says blocking sign-in can take up to 24 hours to take effect, and resetting the password is what stops new sign-ins straight away; that's why you do both. Revoking sessions can take a few minutes, and Outlook on the web may stay open until he refreshes the page or opens another app. - **`Update-MgUser` fails.** Changing `AccountEnabled` is a sensitive action: the scope isn't enough without the right Entra role (see **Make the change**). If the account is synced from on-premises, disable it in Active Directory instead (see **Before you start**). - **Ana doesn't show up when you filter by `-User`.** `Get-EXOMailboxPermission -User` is case sensitive. List all permissions on the mailbox, as above, and look for her in the `User` column. - **The mailbox is over 50 GB, or needs a hold.** A shared mailbox needs a license if it holds more than 50 GB, is placed on litigation hold, or needs a bigger archive. Check before you remove Tom's. - **Ana doesn't want it in her Outlook.** Full Access adds the mailbox to her Outlook automatically by default. Add `-AutoMapping $false` when you grant it; to change that later, remove the permission and add it again. ### The same "it never arrived" question, in Exchange Online If Priya's mailbox were in Exchange Online, you'd use message trace instead of the tracking log and queues (read-only): ```powershell Connect-ExchangeOnline -UserPrincipalName admin@contoso.com Get-MessageTraceV2 -SenderAddress priya.shah@contoso.com -RecipientAddress buyer@example.com -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) Get-MessageTraceV2 -SenderAddress priya.shah@contoso.com -RecipientAddress buyer@example.com -StartDate (Get-Date).AddDays(-2) -EndDate (Get-Date) | Get-MessageTraceDetailV2 # each step, with the reason for a failure Disconnect-ExchangeOnline -Confirm:$false ``` Look at `Status`: `Delivered`, `Failed`, `Pending`, `FilteredAsSpam` or `Quarantined`. Message trace covers the last 90 days, and each query can cover up to 10 days. `Get-MessageTraceV2` replaces the older `Get-MessageTrace`. No change needed: this is a read-only investigation. If the trace shows a failure, read the detail for the reason. ### Quick reference: Exchange Online | Command | What it does | Read-only or changes something | | --- | --- | --- | | `Connect-ExchangeOnline -UserPrincipalName admin@contoso.com` | Signs in to Exchange Online | Read-only (sign-in) | | `Get-EXOMailbox -Identity user@contoso.com` | Shows one mailbox | Read-only | | `Get-EXOMailboxStatistics -Identity user@contoso.com` | Shows item count and mailbox size | Read-only | | `Get-EXOMailboxPermission -Identity user@contoso.com` | Lists who has access to a mailbox | Read-only | | `Get-MgUser -UserId $id -Property 'AccountEnabled,OnPremisesSyncEnabled'` | Shows whether an account is enabled, and whether it's synced from on-premises | Read-only | | `Update-MgUser -UserId $id -AccountEnabled:$false` | Blocks sign-in for a cloud account | Changes the directory | | `Revoke-MgUserSignInSession -UserId $id` | Signs the user out of existing sessions | Changes the directory | | `Disable-ADAccount -Identity tbecker` | Disables an account synced from on-premises Active Directory | Changes the directory | | `Set-Mailbox -Identity user@contoso.com -Type Shared` | Converts a user mailbox to a shared mailbox | Changes the mailbox | | `Add-MailboxPermission -Identity ... -User ... -AccessRights FullAccess -InheritanceType All` | Gives someone Full Access to a mailbox | Changes the mailbox | | `Get-MessageTraceV2 -SenderAddress ... -RecipientAddress ...` | Traces messages from the last 90 days (10 days per query) | Read-only | | `Get-MessageTraceDetailV2` | Shows each step for a traced message | Read-only | | `Disconnect-ExchangeOnline -Confirm:$false` | Ends the session | Read-only (sign-out) | ## Splatting (readable long commands) ```powershell $params = @{ ComputerName = 'PC01' ScriptBlock = { Get-Service Spooler } ErrorAction = 'Stop' } Invoke-Command @params ``` --- ## Sources and official documentation - [Microsoft Learn: PowerShell documentation](https://learn.microsoft.com/en-us/powershell/) - [Microsoft Learn: Differences between Windows PowerShell 5.1 and PowerShell 7.x](https://learn.microsoft.com/en-us/powershell/scripting/whats-new/differences-from-windows-powershell) - [Microsoft Learn: Get-WinEvent](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.diagnostics/get-winevent) - [Microsoft Learn: PowerShell support lifecycle](https://learn.microsoft.com/en-us/powershell/scripting/install/powershell-support-lifecycle) - [Microsoft Learn: About the Exchange Online PowerShell module](https://learn.microsoft.com/en-us/powershell/exchange/exchange-online-powershell-v2) - [Microsoft Learn: about_Execution_Policies](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies) - [Microsoft Learn: Remote Server Administration Tools (RSAT)](https://learn.microsoft.com/en-us/windows-server/remote/remote-server-administration-tools) - [Microsoft Learn: Get-ADUser](https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-aduser) - [Microsoft Learn: Search-ADAccount](https://learn.microsoft.com/en-us/powershell/module/activedirectory/search-adaccount) - [Microsoft Learn: Unlock-ADAccount](https://learn.microsoft.com/en-us/powershell/module/activedirectory/unlock-adaccount) - [Microsoft Learn: Get-ADDomainController](https://learn.microsoft.com/en-us/powershell/module/activedirectory/get-addomaincontroller) - [Microsoft Learn: Install the Microsoft Graph PowerShell SDK](https://learn.microsoft.com/en-us/powershell/microsoftgraph/installation) - [Microsoft Learn: Migrate from Azure AD PowerShell to Microsoft Graph PowerShell (AzureAD and MSOnline deprecated)](https://learn.microsoft.com/en-us/powershell/microsoftgraph/migration-steps) - [Microsoft Learn: Azure AD PowerShell overview (AzureAD and MSOnline deprecated March 30, 2024; working only through March 30, 2025)](https://learn.microsoft.com/en-us/powershell/azure/active-directory/overview) - [Microsoft Learn: Get started with the Microsoft Graph PowerShell SDK (consent to scopes)](https://learn.microsoft.com/en-us/powershell/microsoftgraph/get-started) - [Microsoft Learn: Connect-MgGraph](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/connect-mggraph) - [Microsoft Learn: Disconnect-MgGraph](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/disconnect-mggraph) - [Microsoft Learn: Get-MgUser](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/get-mguser) - [Microsoft Learn: Get-MgGroup](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.groups/get-mggroup) - [Microsoft Learn: Get-MgGroupMember](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.groups/get-mggroupmember) - [Microsoft Learn: New-MgGroupMember](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.groups/new-mggroupmember) - [Microsoft Learn: Microsoft Graph, add members (permissions and roles)](https://learn.microsoft.com/en-us/graph/api/group-post-members) - [Microsoft Learn: Microsoft Graph user get (ID or userPrincipalName)](https://learn.microsoft.com/en-us/graph/api/user-get) - [Microsoft Learn: Add-DistributionGroupMember](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/add-distributiongroupmember) - [Microsoft Learn: Microsoft Graph user resource (accountEnabled, onPremisesSyncEnabled)](https://learn.microsoft.com/en-us/graph/api/resources/user) - [Microsoft Learn: Update-MgUser](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users/update-mguser) - [Microsoft Learn: Microsoft Graph update user (permissions and roles for accountEnabled and passwordProfile)](https://learn.microsoft.com/en-us/graph/api/user-update) - [Microsoft Learn: Revoke-MgUserSignInSession](https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.users.actions/revoke-mgusersigninsession) - [Microsoft Learn: Microsoft Graph user revokeSignInSessions (permissions, delay)](https://learn.microsoft.com/en-us/graph/api/user-revokesigninsessions) - [Microsoft Learn: Revoke user access in an emergency in Microsoft Entra ID (hybrid and cloud steps)](https://learn.microsoft.com/en-us/entra/identity/users/users-revoke-access) - [Microsoft Learn: Disable-ADAccount](https://learn.microsoft.com/en-us/powershell/module/activedirectory/disable-adaccount) - [Microsoft Learn: Prevent a former employee's sign-in and block access to Microsoft 365](https://learn.microsoft.com/en-us/microsoft-365/admin/add-users/remove-former-employee-step-1) - [Microsoft Learn: Assign or unassign licenses for users in the Microsoft 365 admin center](https://learn.microsoft.com/en-us/microsoft-365/admin/manage/assign-licenses-to-users) - [Microsoft Learn: Microsoft Graph group resource (groupTypes, DynamicMembership)](https://learn.microsoft.com/en-us/graph/api/resources/group) - [Microsoft Learn: Dynamic membership rules for groups in Microsoft Entra ID](https://learn.microsoft.com/en-us/entra/identity/users/groups-dynamic-membership) - [Microsoft Learn: Exchange Server supportability matrix (2016 and 2019 end of support)](https://learn.microsoft.com/en-us/exchange/plan-and-deploy/supportability-matrix) - [Microsoft Learn: Test-ServiceHealth](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/test-servicehealth) - [Microsoft Learn: Get-MessageTrackingLog](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-messagetrackinglog) - [Microsoft Learn: Message tracking (event types)](https://learn.microsoft.com/en-us/exchange/mail-flow/transport-logs/message-tracking) - [Microsoft Learn: Get-Queue](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-queue) - [Microsoft Learn: Queue properties](https://learn.microsoft.com/en-us/exchange/mail-flow/queues/queue-properties) - [Microsoft Learn: Retry-Queue](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/retry-queue) - [Microsoft Learn: Get-EXOMailboxStatistics](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-exomailboxstatistics) - [Microsoft Learn: Get-EXOMailboxPermission](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-exomailboxpermission) - [Microsoft Learn: Exchange Online PowerShell property sets](https://learn.microsoft.com/en-us/powershell/exchange/cmdlet-property-sets) - [Microsoft Learn: Set-Mailbox](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/set-mailbox) - [Microsoft Learn: Add-MailboxPermission](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/add-mailboxpermission) - [Microsoft Learn: About shared mailboxes (50 GB without a license)](https://learn.microsoft.com/en-us/microsoft-365/admin/email/about-shared-mailboxes) - [Microsoft Learn: Convert a user mailbox to a shared mailbox](https://learn.microsoft.com/en-us/microsoft-365/admin/email/convert-user-mailbox-to-shared-mailbox) - [Microsoft Learn: Get-MessageTraceV2](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-messagetracev2) - [Microsoft Learn: Get-MessageTraceDetailV2](https://learn.microsoft.com/en-us/powershell/module/exchangepowershell/get-messagetracedetailv2) - [Microsoft Learn: Get-Service (DependentServices)](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-service) - [Microsoft Learn: Restart-Service](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/restart-service) - [Microsoft Learn: Stop-Service (-Force is required to stop a service that has dependent services)](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/stop-service) - [Microsoft Learn: Event ID 7034 from Service Control Manager (service terminated unexpectedly)](https://learn.microsoft.com/en-us/troubleshoot/windows-server/system-management-components/error-1053-1067-7034-after-update-openssh-doesnt-start) - [Microsoft Learn: Event 7031 from Service Control Manager (corrective action in the message)](https://learn.microsoft.com/en-us/troubleshoot/exchange/administration/frontend-transport-service-terminate-run-set-servercomponentstate) - [Microsoft Learn, archived Microsoft documentation (Windows Server 2012): Sc qfailure](https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/cc742047(v=ws.11)) - [Microsoft Learn: Resolve-DnsName (-Server, -DnsOnly, -NoHostsFile)](https://learn.microsoft.com/en-us/powershell/module/dnsclient/resolve-dnsname) - [Microsoft Learn: Test-NetConnection (-CommonTCPPort SMB)](https://learn.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection) - [Microsoft Learn: Clear-DnsClientCache (equivalent to ipconfig /flushdns)](https://learn.microsoft.com/en-us/powershell/module/dnsclient/clear-dnsclientcache) - [Microsoft Learn: Troubleshoot unexpected reboots using system event logs (event IDs 41, 1001, 1074, 6008, 7045)](https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/troubleshoot-unexpected-reboots-system-event-logs) - [Microsoft Learn: Manage device restarts after updates (active hours)](https://learn.microsoft.com/en-us/windows/deployment/update/waas-restart) - [Microsoft Learn: Get-HotFix (returns only updates supplied by Component-Based Servicing)](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/get-hotfix) ## Review history - Technical review: Jeremiah Ellington, 2026-10-25 - Editorial review: Jeremiah Ellington, 2026-10-29