PowerShell for Windows Server Admins: 10 Essential Scripts

PowerShell for Windows Server Admins: 10 Essential Scripts

PowerShell is the command-line backbone of modern Windows Server administration. Whether you are managing one server or a hundred, scripting repetitive tasks saves hours and eliminates human error. These ten scripts cover the tasks every Windows admin faces weekly — learn them, adapt them, and build your own library from here.

1. Get a System Inventory Report

Quickly document a server's hardware and OS details — useful for asset management and troubleshooting:

$info = Get-ComputerInfo
[PSCustomObject]@{
    Hostname    = $env:COMPUTERNAME
    OS          = $info.OsName
    Version     = $info.OsVersion
    RAM_GB      = [math]::Round($info.CsTotalPhysicalMemory / 1GB, 2)
    CPU         = $info.CsProcessors.Name
    LastBoot    = $info.OsLastBootUpTime
} | Format-List

2. Bulk Create Active Directory Users from CSV

Prepare a CSV with columns: FirstName, LastName, Department, OU. Then run:

Import-Csv "C:Temp
ew-users.csv" | ForEach-Object {
    $username = ($_.FirstName[0] + $_.LastName).ToLower()
    $upn = "$username@corp.local"
    New-ADUser `
        -Name "$($_.FirstName) $($_.LastName)" `
        -GivenName $_.FirstName `
        -Surname $_.LastName `
        -SamAccountName $username `
        -UserPrincipalName $upn `
        -Path "OU=$($_.Department),OU=Users,DC=corp,DC=local" `
        -AccountPassword (ConvertTo-SecureString "Welcome1!" -AsPlainText -Force) `
        -ChangePasswordAtLogon $true `
        -Enabled $true
    Write-Host "Created: $upn"
}

3. Find All Inactive User Accounts

Stale accounts are a security risk. Find users who have not logged in for 90 days:

$cutoff = (Get-Date).AddDays(-90)
Get-ADUser -Filter {LastLogonDate -lt $cutoff -and Enabled -eq $true} `
    -Properties LastLogonDate, Department |
    Select-Object Name, SamAccountName, Department, LastLogonDate |
    Sort-Object LastLogonDate |
    Export-Csv "C:Tempinactive-users.csv" -NoTypeInformation

4. Monitor Disk Space Across Multiple Servers

$servers = "srv-dc01", "srv-web01", "srv-files01"
$servers | ForEach-Object {
    Get-WmiObject Win32_LogicalDisk -ComputerName $_ -Filter "DriveType=3" |
    Select-Object `
        @{N="Server";E={$_.__SERVER}},
        DeviceID,
        @{N="Size_GB";E={[math]::Round($_.Size/1GB,1)}},
        @{N="Free_GB";E={[math]::Round($_.FreeSpace/1GB,1)}},
        @{N="Used_%";E={[math]::Round((($_.Size - $_.FreeSpace)/$_.Size)*100,1)}}
} | Format-Table -AutoSize

5. Restart a Service and Verify It Comes Back Up

$serviceName = "Spooler"
$server = "srv-print01"
Restart-Service -Name $serviceName -ComputerName $server
Start-Sleep -Seconds 5
$status = (Get-Service -Name $serviceName -ComputerName $server).Status
if ($status -eq "Running") {
    Write-Host "$serviceName on $server is Running." -ForegroundColor Green
} else {
    Write-Warning "$serviceName on $server is $status — investigate immediately."
}

6. Export All Members of a Security Group

$groupName = "Finance-ReadOnly"
Get-ADGroupMember -Identity $groupName -Recursive |
    Get-ADUser -Properties DisplayName, EmailAddress, Department |
    Select-Object DisplayName, SamAccountName, EmailAddress, Department |
    Export-Csv "C:Temp$groupName-members.csv" -NoTypeInformation

7. Check Windows Update Status

$session = New-Object -ComObject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$results = $searcher.Search("IsInstalled=0 and Type='Software'")
Write-Host "Pending updates: $($results.Updates.Count)"
$results.Updates | Select-Object Title, MsrcSeverity | Format-Table -AutoSize

8. Find and Kill Processes Consuming High CPU

Get-Process | Sort-Object CPU -Descending | Select-Object -First 10 Name, Id, CPU, WorkingSet |
    ForEach-Object {
        $_ | Add-Member -NotePropertyName RAM_MB -NotePropertyValue ([math]::Round($_.WorkingSet/1MB,1)) -PassThru
    } | Select-Object Name, Id, CPU, RAM_MB | Format-Table -AutoSize

9. Create a Local Admin Account on Remote Servers

$servers = "srv-web01","srv-app01"
$password = Read-Host "Enter password" -AsSecureString
foreach ($srv in $servers) {
    Invoke-Command -ComputerName $srv -ScriptBlock {
        param($pw)
        New-LocalUser -Name "svc-admin" -Password $pw -FullName "Local Admin" -Description "Break-glass account" -PasswordNeverExpires $true
        Add-LocalGroupMember -Group "Administrators" -Member "svc-admin"
    } -ArgumentList $password
    Write-Host "Account created on $srv"
}

10. Generate an HTML Server Health Report

$report = @"

Server Health: $env:COMPUTERNAME

Generated: $(Get-Date)

Top 5 Processes by CPU

$(Get-Process | Sort-Object CPU -Descending | Select-Object -First 5 Name, CPU | ConvertTo-Html -Fragment)

Disk Space

$(Get-PSDrive -PSProvider FileSystem | Select-Object Name, Used, Free | ConvertTo-Html -Fragment) "@ $report | Out-File "C:Temphealth-report.html" Invoke-Item "C:Temphealth-report.html"

Save your scripts in a structured folder like C:Scripts and version-control them with Git. A personal script library is one of the most valuable assets a Windows admin can build over their career.

Running PowerShell Scripts: Execution Policy Explained

By default, Windows blocks all PowerShell scripts from running — a policy intended to prevent accidental execution of downloaded scripts. Before you can run your first .ps1 file, you need to understand and set the appropriate execution policy.

The most commonly used policies are: Restricted (default — no scripts at all), RemoteSigned (local scripts run freely; scripts downloaded from the internet must be digitally signed by a trusted publisher), and Unrestricted (all scripts run, but downloaded scripts trigger a confirmation prompt). For server administration work, RemoteSigned is the standard production setting — it is permissive enough to run your own scripts while blocking untrusted downloaded ones.

# View the current policy
Get-ExecutionPolicy

# Set to RemoteSigned for the local machine (requires Administrator)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachine

# Run a one-off script bypassing policy (without permanently changing it)
powershell.exe -ExecutionPolicy Bypass -File .\myscript.ps1

Always set execution policy intentionally and document why, especially on servers. Avoid Unrestricted in production; RemoteSigned provides a practical balance of usability and security.

Frequently Asked Questions

  • What is the difference between PowerShell 5.1 and PowerShell 7?
    Windows PowerShell 5.1 ships built into Windows and will not receive new features — it is maintained for compatibility only. PowerShell 7 is the actively developed, cross-platform successor built on .NET Core. It runs on Windows, Linux, and macOS, supports newer language features, and has better performance. For new scripts, target PowerShell 7; for Windows-only management that depends on legacy WMI or COM modules that only work in 5.1, stick with the built-in version. Both can coexist on the same machine.
  • How do I connect to a remote Windows Server with PowerShell?
    Use PowerShell Remoting, which runs over WS-Management (WinRM). Enable it on the target server with Enable-PSRemoting -Force from an elevated prompt. Then from your workstation: Enter-PSSession -ComputerName ServerName -Credential (Get-Credential) for an interactive session, or Invoke-Command -ComputerName ServerName -ScriptBlock { Get-Service } to run a command remotely. Ensure WinRM is allowed through the Windows Firewall and that the target server is either in the same domain or added to the local TrustedHosts list.
  • How do I schedule a PowerShell script to run automatically?
    Use Windows Task Scheduler. The action should be: Program = powershell.exe, Arguments = -ExecutionPolicy Bypass -NonInteractive -File "C:\Scripts\myscript.ps1". The -NonInteractive flag prevents any prompts that would hang the task. Run the task as a dedicated service account with the minimum required permissions rather than as your personal account or SYSTEM. Test by right-clicking the task and selecting "Run" to confirm it works in the scheduler's context before relying on automatic triggers.
  • What does $_ mean in PowerShell?
    $_ is the automatic variable representing the current object in the pipeline. When you pipe objects through Where-Object, ForEach-Object, or Select-Object, $_ refers to the object currently being processed. For example, Get-Process | Where-Object { $_.CPU -gt 10 } filters to processes where the CPU property exceeds 10. You can also write the more readable alias $PSItem in newer scripts. Understanding $_ is fundamental to writing effective PowerShell pipelines.