Windows Server 2022 PowerShell Automation: Enterprise Management
PowerShell transforms Windows Server administration from manual tasks to automated, scalable operations. This comprehensive guide covers PowerShell automation for Windows Server 2022, from basic server management to enterprise-wide deployments.
PowerShell Fundamentals for Server Management
Initial Server Configuration
# Server Initial Configuration Script
# Run as Administrator on fresh Windows Server 2022 installation
#Requires -RunAsAdministrator
#Requires -Version 5.1
param(
[Parameter(Mandatory=$true)]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[string]$IPAddress,
[Parameter(Mandatory=$true)]
[string]$DefaultGateway,
[Parameter(Mandatory=$true)]
[string[]]$DNSServers,
[Parameter(Mandatory=$true)]
[string]$DomainName,
[Parameter(Mandatory=$true)]
[SecureString]$DomainAdminPassword
)
# Set execution policy
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force
# Configure Windows Error Reporting
Write-Host "Configuring Windows Error Reporting..." -ForegroundColor Yellow
Disable-WindowsErrorReporting
# Set computer name
Write-Host "Setting computer name to $ComputerName..." -ForegroundColor Yellow
Rename-Computer -NewName $ComputerName -Force
# Configure network settings
Write-Host "Configuring network settings..." -ForegroundColor Yellow
$adapter = Get-NetAdapter | Where-Object {$_.Status -eq "Up"} | Select-Object -First 1
# Remove existing IP configuration
Remove-NetIPAddress -InterfaceIndex $adapter.InterfaceIndex -Confirm:$false -ErrorAction SilentlyContinue
Remove-NetRoute -InterfaceIndex $adapter.InterfaceIndex -Confirm:$false -ErrorAction SilentlyContinue
# Set static IP
New-NetIPAddress -InterfaceIndex $adapter.InterfaceIndex `
-IPAddress $IPAddress `
-PrefixLength 24 `
-DefaultGateway $DefaultGateway
# Set DNS servers
Set-DnsClientServerAddress -InterfaceIndex $adapter.InterfaceIndex `
-ServerAddresses $DNSServers
# Configure time zone
Write-Host "Setting time zone..." -ForegroundColor Yellow
Set-TimeZone -Name "Eastern Standard Time"
# Enable Remote Desktop
Write-Host "Enabling Remote Desktop..." -ForegroundColor Yellow
Set-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' `
-Name "fDenyTSConnections" -Value 0
Enable-NetFirewallRule -DisplayGroup "Remote Desktop"
# Configure Windows Firewall
Write-Host "Configuring Windows Firewall..." -ForegroundColor Yellow
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
# Enable PowerShell Remoting
Write-Host "Enabling PowerShell Remoting..." -ForegroundColor Yellow
Enable-PSRemoting -Force
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force
# Configure Windows Update
Write-Host "Configuring Windows Update..." -ForegroundColor Yellow
Install-Module PSWindowsUpdate -Force -SkipPublisherCheck
Import-Module PSWindowsUpdate
Add-WUServiceManager -ServiceID "7971f918-a847-4430-9279-4a52d1efe18d" -AddServiceFlag 7
# Install common features
Write-Host "Installing common Windows features..." -ForegroundColor Yellow
$features = @(
"NET-Framework-Core",
"NET-Framework-45-Core",
"PowerShell-ISE",
"Telnet-Client",
"Windows-Server-Backup"
)
foreach ($feature in $features) {
Install-WindowsFeature -Name $feature -IncludeManagementTools
}
# Join domain
Write-Host "Joining domain $DomainName..." -ForegroundColor Yellow
$credential = New-Object System.Management.Automation.PSCredential `
("$DomainName\Administrator", $DomainAdminPassword)
Add-Computer -DomainName $DomainName -Credential $credential -Force
Write-Host "Initial configuration complete. Server will restart in 10 seconds..." -ForegroundColor Green
Start-Sleep -Seconds 10
Restart-Computer -Force
Advanced PowerShell Profile Configuration
# Microsoft.PowerShell_profile.ps1
# Place in $HOME\Documents\WindowsPowerShell\
# Set console appearance
$host.UI.RawUI.WindowTitle = "PowerShell - $env:COMPUTERNAME"
$host.UI.RawUI.BackgroundColor = "Black"
$host.UI.RawUI.ForegroundColor = "Green"
# Custom prompt
function prompt {
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
$principal = [Security.Principal.WindowsPrincipal] $identity
$adminRole = [Security.Principal.WindowsBuiltInRole]::Administrator
$prefix = ""
if ($principal.IsInRole($adminRole)) {
$prefix = "[ADMIN] "
}
Write-Host "$prefix" -NoNewline -ForegroundColor Red
Write-Host "$env:USERNAME@$env:COMPUTERNAME" -NoNewline -ForegroundColor Yellow
Write-Host " $(Get-Location)" -NoNewline -ForegroundColor Cyan
Write-Host " >" -NoNewline -ForegroundColor White
return " "
}
# Aliases and functions
Set-Alias -Name npp -Value "C:\Program Files\Notepad++\notepad++.exe"
Set-Alias -Name grep -Value Select-String
function Get-SystemInfo {
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$cpu = Get-CimInstance Win32_Processor
$mem = Get-CimInstance Win32_PhysicalMemory
[PSCustomObject]@{
ComputerName = $cs.Name
Domain = $cs.Domain
OS = $os.Caption
Version = $os.Version
Architecture = $os.OSArchitecture
CPUName = $cpu.Name
CPUCores = $cpu.NumberOfCores
TotalMemoryGB = [math]::Round(($mem | Measure-Object -Property Capacity -Sum).Sum / 1GB, 2)
LastBoot = $os.LastBootUpTime
}
}
function Get-DiskSpace {
Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3" |
Select-Object DeviceID,
@{n='Size(GB)';e={[math]::Round($_.Size/1GB,2)}},
@{n='Free(GB)';e={[math]::Round($_.FreeSpace/1GB,2)}},
@{n='Used(GB)';e={[math]::Round(($_.Size-$_.FreeSpace)/1GB,2)}},
@{n='%Used';e={[math]::Round((($_.Size-$_.FreeSpace)/$_.Size)*100,2)}}
}
# Load modules
Import-Module ActiveDirectory -ErrorAction SilentlyContinue
Import-Module ServerManager -ErrorAction SilentlyContinue
# Set location to scripts directory
Set-Location C:\Scripts
Active Directory Automation
User and Group Management
# Active Directory User Management Module
# Save as ADUserManagement.psm1
function New-BulkADUsers {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$CSVPath,
[Parameter(Mandatory=$true)]
[string]$DefaultPassword,
[Parameter(Mandatory=$false)]
[string]$LogPath = "C:\Logs\ADUserCreation.log"
)
# Initialize logging
Start-Transcript -Path $LogPath -Append
# Import CSV
$users = Import-Csv -Path $CSVPath
foreach ($user in $users) {
try {
# Generate username
$username = ($user.FirstName.Substring(0,1) + $user.LastName).ToLower()
$upn = "$username@$((Get-ADDomain).DNSRoot)"
# Check if user exists
if (Get-ADUser -Filter "SamAccountName -eq '$username'" -ErrorAction SilentlyContinue) {
Write-Warning "User $username already exists. Skipping..."
continue
}
# Create user parameters
$userParams = @{
Name = "$($user.FirstName) $($user.LastName)"
GivenName = $user.FirstName
Surname = $user.LastName
SamAccountName = $username
UserPrincipalName = $upn
DisplayName = "$($user.FirstName) $($user.LastName)"
Description = $user.JobTitle
Department = $user.Department
Company = $user.Company
Office = $user.Office
EmailAddress = "$username@company.com"
AccountPassword = (ConvertTo-SecureString $DefaultPassword -AsPlainText -Force)
Enabled = $true
ChangePasswordAtLogon = $true
Path = "OU=$($user.Department),OU=Users,DC=company,DC=local"
}
# Create the user
New-ADUser @userParams
Write-Host "Created user: $username" -ForegroundColor Green
# Add to groups
if ($user.Groups) {
$groups = $user.Groups -split ';'
foreach ($group in $groups) {
Add-ADGroupMember -Identity $group.Trim() -Members $username
Write-Host " Added to group: $group" -ForegroundColor Cyan
}
}
# Set additional attributes
Set-ADUser -Identity $username -Replace @{
'msExchHideFromAddressLists' = $false
'extensionAttribute1' = $user.EmployeeID
'extensionAttribute2' = $user.CostCenter
}
} catch {
Write-Error "Failed to create user $($user.FirstName) $($user.LastName): $_"
}
}
Stop-Transcript
}
function Get-ADUserReport {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[int]$InactiveDays = 90,
[Parameter(Mandatory=$false)]
[string]$ExportPath = "C:\Reports\ADUserReport_$(Get-Date -Format 'yyyyMMdd').csv"
)
$inactiveDate = (Get-Date).AddDays(-$InactiveDays)
$users = Get-ADUser -Filter * -Properties * | Select-Object `
Name,
SamAccountName,
EmailAddress,
Department,
Title,
Manager,
Enabled,
LastLogonDate,
PasswordLastSet,
PasswordNeverExpires,
AccountExpirationDate,
@{n='Groups';e={(Get-ADPrincipalGroupMembership $_.SamAccountName |
Where-Object {$_.Name -ne 'Domain Users'}).Name -join ';'}},
@{n='IsInactive';e={$_.LastLogonDate -lt $inactiveDate}},
@{n='DaysSinceLastLogon';e={
if ($_.LastLogonDate) {
(New-TimeSpan -Start $_.LastLogonDate -End (Get-Date)).Days
} else { "Never" }
}}
$users | Export-Csv -Path $ExportPath -NoTypeInformation
# Generate summary
$summary = @{
TotalUsers = $users.Count
EnabledUsers = ($users | Where-Object {$_.Enabled}).Count
DisabledUsers = ($users | Where-Object {-not $_.Enabled}).Count
InactiveUsers = ($users | Where-Object {$_.IsInactive}).Count
PasswordNeverExpires = ($users | Where-Object {$_.PasswordNeverExpires}).Count
}
Write-Host "`nActive Directory User Summary:" -ForegroundColor Yellow
$summary.GetEnumerator() | ForEach-Object {
Write-Host "$($_.Key): $($_.Value)" -ForegroundColor Cyan
}
return $users
}
function Set-BulkADUserAttributes {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$SearchBase,
[Parameter(Mandatory=$true)]
[hashtable]$Attributes
)
$users = Get-ADUser -SearchBase $SearchBase -Filter *
foreach ($user in $users) {
try {
Set-ADUser -Identity $user -Replace $Attributes
Write-Host "Updated user: $($user.Name)" -ForegroundColor Green
} catch {
Write-Error "Failed to update user $($user.Name): $_"
}
}
}
Export-ModuleMember -Function *
Group Policy Automation
# Group Policy Management Automation
function New-StandardGPO {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$GPOName,
[Parameter(Mandatory=$true)]
[ValidateSet('Computer','User','Both')]
[string]$PolicyType,
[Parameter(Mandatory=$true)]
[string]$LinkedOU
)
try {
# Create new GPO
$gpo = New-GPO -Name $GPOName -Comment "Created by PowerShell automation on $(Get-Date)"
# Configure computer policies
if ($PolicyType -in 'Computer','Both') {
# Security settings
Set-GPRegistryValue -Name $GPOName -Key "HKLM\Software\Policies\Microsoft\Windows\System" `
-ValueName "EnableSmartScreen" -Type DWord -Value 2
Set-GPRegistryValue -Name $GPOName -Key "HKLM\Software\Policies\Microsoft\Windows Defender" `
-ValueName "DisableAntiSpyware" -Type DWord -Value 0
# Windows Update settings
Set-GPRegistryValue -Name $GPOName -Key "HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" `
-ValueName "NoAutoUpdate" -Type DWord -Value 0
Set-GPRegistryValue -Name $GPOName -Key "HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU" `
-ValueName "AUOptions" -Type DWord -Value 4
# Power settings
Set-GPRegistryValue -Name $GPOName -Key "HKLM\Software\Policies\Microsoft\Power\PowerSettings\0e796bdb-100d-47d6-a2d5-f7d2daa51f51" `
-ValueName "DCSettingIndex" -Type DWord -Value 0
}
# Configure user policies
if ($PolicyType -in 'User','Both') {
# Desktop settings
Set-GPRegistryValue -Name $GPOName -Key "HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System" `
-ValueName "Wallpaper" -Type String -Value "C:\Windows\Web\Wallpaper\Corporate.jpg"
# Internet Explorer settings
Set-GPRegistryValue -Name $GPOName -Key "HKCU\Software\Policies\Microsoft\Internet Explorer\Main" `
-ValueName "Start Page" -Type String -Value "https://intranet.company.com"
# Control Panel restrictions
Set-GPRegistryValue -Name $GPOName -Key "HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer" `
-ValueName "NoControlPanel" -Type DWord -Value 0
}
# Link GPO to OU
New-GPLink -Name $GPOName -Target $LinkedOU -LinkEnabled Yes
Write-Host "GPO '$GPOName' created and linked to $LinkedOU" -ForegroundColor Green
} catch {
Write-Error "Failed to create GPO: $_"
}
}
function Backup-AllGPOs {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$BackupPath
)
$backupFolder = Join-Path $BackupPath "GPO_Backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
New-Item -ItemType Directory -Path $backupFolder -Force | Out-Null
$gpos = Get-GPO -All
foreach ($gpo in $gpos) {
try {
$backupInfo = Backup-GPO -Name $gpo.DisplayName -Path $backupFolder
Write-Host "Backed up GPO: $($gpo.DisplayName)" -ForegroundColor Green
# Export additional information
$gpoInfo = @{
Name = $gpo.DisplayName
Id = $gpo.Id
CreationTime = $gpo.CreationTime
ModificationTime = $gpo.ModificationTime
WmiFilter = $gpo.WmiFilter
Description = $gpo.Description
BackupId = $backupInfo.Id
}
$gpoInfo | Export-Clixml -Path (Join-Path $backupFolder "$($gpo.Id).xml")
} catch {
Write-Error "Failed to backup GPO $($gpo.DisplayName): $_"
}
}
# Create HTML report
$htmlReport = @"
<!DOCTYPE html>
<html>
<head>
<title>GPO Backup Report</title>
<style>
body { font-family: Arial, sans-serif; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
</style>
</head>
<body>
<h1>Group Policy Backup Report</h1>
<p>Backup Date: $(Get-Date)</p>
<p>Total GPOs: $($gpos.Count)</p>
<table>
<tr>
<th>GPO Name</th>
<th>ID</th>
<th>Created</th>
<th>Modified</th>
<th>Links</th>
</tr>
"@
foreach ($gpo in $gpos) {
$links = Get-GPOReport -Name $gpo.DisplayName -ReportType Xml |
Select-Xml -XPath "//LinksTo/SOMPath" |
ForEach-Object { $_.Node.InnerText }
$htmlReport += @"
<tr>
<td>$($gpo.DisplayName)</td>
<td>$($gpo.Id)</td>
<td>$($gpo.CreationTime)</td>
<td>$($gpo.ModificationTime)</td>
<td>$($links -join '<br>')</td>
</tr>
"@
}
$htmlReport += @"
</table>
</body>
</html>
"@
$htmlReport | Out-File -FilePath (Join-Path $backupFolder "BackupReport.html")
Write-Host "`nGPO backup completed. Report saved to: $backupFolder\BackupReport.html" -ForegroundColor Yellow
}
Server Role Automation
IIS Web Server Management
# IIS Automation Module
function Install-IISWithFeatures {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string[]]$AdditionalFeatures = @()
)
$features = @(
"IIS-WebServerRole",
"IIS-WebServer",
"IIS-CommonHttpFeatures",
"IIS-HttpErrors",
"IIS-HttpRedirect",
"IIS-ApplicationDevelopment",
"IIS-NetFxExtensibility45",
"IIS-HealthAndDiagnostics",
"IIS-HttpLogging",
"IIS-Security",
"IIS-RequestFiltering",
"IIS-Performance",
"IIS-WebServerManagementTools",
"IIS-IIS6ManagementCompatibility",
"IIS-Metabase",
"IIS-ASPNET45",
"IIS-NetFx4Extended-ASPNET45",
"IIS-ManagementConsole"
) + $AdditionalFeatures
foreach ($feature in $features) {
Enable-WindowsOptionalFeature -Online -FeatureName $feature -All -NoRestart
Write-Host "Installed feature: $feature" -ForegroundColor Green
}
# Install IIS Management Tools
Import-Module WebAdministration
Write-Host "IIS installation completed" -ForegroundColor Yellow
}
function New-IISWebsite {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$SiteName,
[Parameter(Mandatory=$true)]
[string]$PhysicalPath,
[Parameter(Mandatory=$true)]
[int]$Port,
[Parameter(Mandatory=$false)]
[string]$HostHeader = "",
[Parameter(Mandatory=$false)]
[string]$AppPoolName = $SiteName,
[Parameter(Mandatory=$false)]
[ValidateSet('v2.0','v4.0')]
[string]$RuntimeVersion = 'v4.0',
[Parameter(Mandatory=$false)]
[ValidateSet('Integrated','Classic')]
[string]$PipelineMode = 'Integrated'
)
Import-Module WebAdministration
try {
# Create physical directory
if (-not (Test-Path $PhysicalPath)) {
New-Item -ItemType Directory -Path $PhysicalPath -Force
Write-Host "Created directory: $PhysicalPath" -ForegroundColor Green
}
# Create application pool
if (-not (Test-Path "IIS:\AppPools\$AppPoolName")) {
New-WebAppPool -Name $AppPoolName
Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -Name managedRuntimeVersion -Value $RuntimeVersion
Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -Name managedPipelineMode -Value $PipelineMode
Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -Name enable32BitAppOnWin64 -Value $false
# Configure recycling
Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -Name recycling.periodicRestart.time -Value "00:00:00"
Set-ItemProperty -Path "IIS:\AppPools\$AppPoolName" -Name recycling.periodicRestart.schedule -Value @{value="03:00:00"}
Write-Host "Created application pool: $AppPoolName" -ForegroundColor Green
}
# Create website
$binding = @{protocol="http";bindingInformation="*:${Port}:${HostHeader}"}
New-Website -Name $SiteName -PhysicalPath $PhysicalPath -Port $Port -ApplicationPool $AppPoolName
# Set permissions
$acl = Get-Acl $PhysicalPath
$permission = "IIS_IUSRS","ReadAndExecute","ContainerInherit,ObjectInherit","None","Allow"
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule $permission
$acl.SetAccessRule($accessRule)
Set-Acl $PhysicalPath $acl
# Configure website settings
Set-WebConfigurationProperty -Filter "/system.webServer/directoryBrowse" `
-PSPath "IIS:\Sites\$SiteName" -Name "enabled" -Value $false
Set-WebConfigurationProperty -Filter "/system.webServer/security/requestFiltering" `
-PSPath "IIS:\Sites\$SiteName" -Name "removeServerHeader" -Value $true
# Create default document
$defaultDoc = @"
<!DOCTYPE html>
<html>
<head>
<title>$SiteName</title>
</head>
<body>
<h1>Welcome to $SiteName</h1>
<p>Site is running on IIS</p>
</body>
</html>
"@
$defaultDoc | Out-File -FilePath "$PhysicalPath\index.html" -Encoding UTF8
Write-Host "Website '$SiteName' created successfully" -ForegroundColor Yellow
} catch {
Write-Error "Failed to create website: $_"
}
}
function Set-IISSecurityHeaders {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$SiteName
)
$headers = @{
"X-Content-Type-Options" = "nosniff"
"X-Frame-Options" = "SAMEORIGIN"
"X-XSS-Protection" = "1; mode=block"
"Strict-Transport-Security" = "max-age=31536000; includeSubDomains"
"Content-Security-Policy" = "default-src 'self'"
"Referrer-Policy" = "strict-origin-when-cross-origin"
}
foreach ($header in $headers.GetEnumerator()) {
Add-WebConfigurationProperty -PSPath "IIS:\Sites\$SiteName" `
-Filter "system.webServer/httpProtocol/customHeaders" `
-Name "." -Value @{name=$header.Key;value=$header.Value}
Write-Host "Added security header: $($header.Key)" -ForegroundColor Green
}
}
File Server Management
# File Server Resource Manager Automation
function Configure-FileServer {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$SharePath,
[Parameter(Mandatory=$true)]
[string]$ShareName,
[Parameter(Mandatory=$false)]
[string]$Description = "Managed by PowerShell"
)
# Install File Server features
$features = @(
"FS-FileServer",
"FS-Resource-Manager",
"FS-DFS-Namespace",
"FS-DFS-Replication",
"FS-VSS-Agent"
)
foreach ($feature in $features) {
Install-WindowsFeature -Name $feature -IncludeManagementTools
}
# Create share directory
if (-not (Test-Path $SharePath)) {
New-Item -ItemType Directory -Path $SharePath -Force
}
# Create SMB share
New-SmbShare -Name $ShareName `
-Path $SharePath `
-Description $Description `
-FullAccess "Domain Admins" `
-ChangeAccess "Domain Users" `
-FolderEnumerationMode AccessBased `
-CachingMode Documents `
-EncryptData $true
# Configure NTFS permissions
$acl = Get-Acl $SharePath
# Remove inherited permissions
$acl.SetAccessRuleProtection($true, $false)
# Add specific permissions
$permissions = @(
@{Identity="Domain Admins"; Rights="FullControl"; Type="Allow"},
@{Identity="Domain Users"; Rights="ReadAndExecute,Write"; Type="Allow"},
@{Identity="CREATOR OWNER"; Rights="FullControl"; Type="Allow"}
)
foreach ($perm in $permissions) {
$accessRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$perm.Identity,
$perm.Rights,
"ContainerInherit,ObjectInherit",
"None",
$perm.Type
)
$acl.AddAccessRule($accessRule)
}
Set-Acl -Path $SharePath -AclObject $acl
# Configure File Screen
New-FsrmFileScreen -Path $SharePath `
-Template "Block Executable Files" `
-Description "Blocks potentially dangerous file types"
# Set up quota
New-FsrmQuota -Path $SharePath `
-Template "100 MB Limit" `
-Description "User folder quota"
Write-Host "File share '$ShareName' configured successfully" -ForegroundColor Green
}
function New-UserHomeDirectories {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$BasePath,
[Parameter(Mandatory=$false)]
[string]$SearchBase = (Get-ADDomain).DistinguishedName
)
# Get all AD users
$users = Get-ADUser -SearchBase $SearchBase -Filter * -Properties HomeDirectory, HomeDrive
foreach ($user in $users) {
$homePath = Join-Path $BasePath $user.SamAccountName
# Create home directory
if (-not (Test-Path $homePath)) {
New-Item -ItemType Directory -Path $homePath -Force
# Set permissions
$acl = Get-Acl $homePath
$acl.SetAccessRuleProtection($true, $false)
# Domain Admins - Full Control
$adminRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
"Domain Admins",
"FullControl",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)
$acl.AddAccessRule($adminRule)
# User - Full Control
$userRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$user.SamAccountName,
"FullControl",
"ContainerInherit,ObjectInherit",
"None",
"Allow"
)
$acl.AddAccessRule($userRule)
Set-Acl -Path $homePath -AclObject $acl
# Update AD user properties
Set-ADUser -Identity $user -HomeDirectory "\\$env:COMPUTERNAME\Users$\$($user.SamAccountName)" -HomeDrive "H:"
Write-Host "Created home directory for: $($user.SamAccountName)" -ForegroundColor Green
}
}
}
System Monitoring and Maintenance
Performance Monitoring
# System Performance Monitoring Module
function Start-PerformanceMonitoring {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[int]$DurationMinutes = 60,
[Parameter(Mandatory=$false)]
[int]$SampleInterval = 5,
[Parameter(Mandatory=$false)]
[string]$OutputPath = "C:\PerfLogs\PerfMon_$(Get-Date -Format 'yyyyMMdd_HHmmss').blg"
)
# Create output directory
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force
}
# Define counters
$counters = @(
"\Processor(_Total)\% Processor Time",
"\Memory\Available MBytes",
"\Memory\Pages/sec",
"\PhysicalDisk(_Total)\Disk Reads/sec",
"\PhysicalDisk(_Total)\Disk Writes/sec",
"\PhysicalDisk(_Total)\Avg. Disk Queue Length",
"\Network Interface(*)\Bytes Total/sec",
"\System\Processor Queue Length",
"\Process(_Total)\Handle Count",
"\Process(_Total)\Thread Count"
)
# Create data collector set
$datacollectorset = New-Object -COM Pla.DataCollectorSet
$datacollectorset.DisplayName = "PowerShell Performance Monitor"
$datacollectorset.Duration = $DurationMinutes * 60
$datacollectorset.SubdirectoryFormat = 1
$datacollectorset.SubdirectoryFormatPattern = "yyyy-MM-dd"
$datacollectorset.RootPath = Split-Path $OutputPath -Parent
$datacollector = $datacollectorset.DataCollectors.CreateDataCollector(0)
$datacollector.FileName = [System.IO.Path]::GetFileNameWithoutExtension($OutputPath)
$datacollector.FileNameFormat = 0
$datacollector.FileNameFormatPattern = ""
$datacollector.SampleInterval = $SampleInterval
$datacollector.LogFileFormat = 3 # Binary
$counters | ForEach-Object { $datacollector.PerformanceCounters.Add($_) | Out-Null }
$datacollectorset.DataCollectors.Add($datacollector)
try {
$datacollectorset.Commit("TempPerfMon", $null, 0x0003) | Out-Null
$datacollectorset.Start($false)
Write-Host "Performance monitoring started. Duration: $DurationMinutes minutes" -ForegroundColor Green
Write-Host "Output file: $OutputPath" -ForegroundColor Yellow
} catch {
Write-Error "Failed to start performance monitoring: $_"
}
}
function Get-SystemHealthReport {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string]$OutputPath = "C:\Reports\SystemHealth_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
# Gather system information
$os = Get-CimInstance Win32_OperatingSystem
$cs = Get-CimInstance Win32_ComputerSystem
$cpu = Get-CimInstance Win32_Processor
$mem = Get-CimInstance Win32_PhysicalMemory
$disk = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
# Get event log errors
$criticalEvents = Get-EventLog -LogName System -EntryType Error -Newest 50
$appErrors = Get-EventLog -LogName Application -EntryType Error -Newest 50
# Get service status
$stoppedServices = Get-Service | Where-Object {
$_.StartType -eq 'Automatic' -and $_.Status -ne 'Running'
}
# Generate HTML report
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>System Health Report - $($cs.Name)</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h1, h2 { color: #333; }
table { border-collapse: collapse; width: 100%; margin-bottom: 20px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.warning { background-color: #ff9800; color: white; }
.error { background-color: #f44336; color: white; }
.success { background-color: #4CAF50; color: white; }
</style>
</head>
<body>
<h1>System Health Report</h1>
<p>Generated: $(Get-Date)</p>
<h2>System Information</h2>
<table>
<tr><th>Property</th><th>Value</th></tr>
<tr><td>Computer Name</td><td>$($cs.Name)</td></tr>
<tr><td>Domain</td><td>$($cs.Domain)</td></tr>
<tr><td>Operating System</td><td>$($os.Caption)</td></tr>
<tr><td>Version</td><td>$($os.Version)</td></tr>
<tr><td>Last Boot Time</td><td>$($os.LastBootUpTime)</td></tr>
<tr><td>Uptime</td><td>$((Get-Date) - $os.LastBootUpTime)</td></tr>
</table>
<h2>Hardware Information</h2>
<table>
<tr><th>Component</th><th>Details</th></tr>
<tr><td>CPU</td><td>$($cpu.Name) ($($cpu.NumberOfCores) cores)</td></tr>
<tr><td>Total Memory</td><td>$([math]::Round(($mem | Measure-Object -Property Capacity -Sum).Sum / 1GB, 2)) GB</td></tr>
<tr><td>Available Memory</td><td>$([math]::Round($os.FreePhysicalMemory / 1MB, 2)) GB</td></tr>
</table>
<h2>Disk Space</h2>
<table>
<tr><th>Drive</th><th>Total (GB)</th><th>Free (GB)</th><th>Used %</th><th>Status</th></tr>
"@
foreach ($d in $disk) {
$totalGB = [math]::Round($d.Size / 1GB, 2)
$freeGB = [math]::Round($d.FreeSpace / 1GB, 2)
$usedPercent = [math]::Round((($d.Size - $d.FreeSpace) / $d.Size) * 100, 2)
$statusClass = if ($usedPercent -gt 90) { "error" }
elseif ($usedPercent -gt 80) { "warning" }
else { "success" }
$html += @"
<tr>
<td>$($d.DeviceID)</td>
<td>$totalGB</td>
<td>$freeGB</td>
<td class="$statusClass">$usedPercent%</td>
<td class="$statusClass">$(if ($usedPercent -gt 90) { "Critical" } elseif ($usedPercent -gt 80) { "Warning" } else { "OK" })</td>
</tr>
"@
}
$html += @"
</table>
<h2>Stopped Services</h2>
"@
if ($stoppedServices) {
$html += @"
<table>
<tr><th>Service Name</th><th>Display Name</th><th>Start Type</th></tr>
"@
foreach ($service in $stoppedServices) {
$html += @"
<tr>
<td>$($service.Name)</td>
<td>$($service.DisplayName)</td>
<td>$($service.StartType)</td>
</tr>
"@
}
$html += "</table>"
} else {
$html += "<p>All automatic services are running.</p>"
}
$html += @"
<h2>Recent System Errors (Last 10)</h2>
<table>
<tr><th>Time</th><th>Source</th><th>Message</th></tr>
"@
foreach ($event in ($criticalEvents | Select-Object -First 10)) {
$html += @"
<tr>
<td>$($event.TimeGenerated)</td>
<td>$($event.Source)</td>
<td>$($event.Message.Substring(0, [Math]::Min(200, $event.Message.Length)))...</td>
</tr>
"@
}
$html += @"
</table>
</body>
</html>
"@
# Save report
$outputDir = Split-Path $OutputPath -Parent
if (-not (Test-Path $outputDir)) {
New-Item -ItemType Directory -Path $outputDir -Force
}
$html | Out-File -FilePath $OutputPath -Encoding UTF8
Write-Host "System health report generated: $OutputPath" -ForegroundColor Green
# Open report in browser
Start-Process $OutputPath
}
Security Automation
Security Baseline Configuration
# Windows Server Security Hardening Module
function Apply-SecurityBaseline {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[ValidateSet('Basic','Moderate','High')]
[string]$SecurityLevel = 'Moderate'
)
Write-Host "Applying $SecurityLevel security baseline..." -ForegroundColor Yellow
# Account Policies
Write-Host "Configuring account policies..." -ForegroundColor Cyan
# Password Policy
$passwordPolicy = @{
'MinimumPasswordLength' = if ($SecurityLevel -eq 'High') { 15 } elseif ($SecurityLevel -eq 'Moderate') { 12 } else { 8 }
'PasswordComplexity' = $true
'MaximumPasswordAge' = if ($SecurityLevel -eq 'High') { 60 } elseif ($SecurityLevel -eq 'Moderate') { 90 } else { 180 }
'MinimumPasswordAge' = 1
'PasswordHistorySize' = if ($SecurityLevel -eq 'High') { 24 } elseif ($SecurityLevel -eq 'Moderate') { 12 } else { 6 }
}
# Apply using secedit
$secTemplate = @"
[Unicode]
Unicode=yes
[System Access]
MinimumPasswordAge = $($passwordPolicy.MinimumPasswordAge)
MaximumPasswordAge = $($passwordPolicy.MaximumPasswordAge)
MinimumPasswordLength = $($passwordPolicy.MinimumPasswordLength)
PasswordComplexity = $(if ($passwordPolicy.PasswordComplexity) { 1 } else { 0 })
PasswordHistorySize = $($passwordPolicy.PasswordHistorySize)
LockoutBadCount = $(if ($SecurityLevel -eq 'High') { 3 } elseif ($SecurityLevel -eq 'Moderate') { 5 } else { 10 })
LockoutDuration = 30
ResetLockoutCount = 30
"@
$tempFile = [System.IO.Path]::GetTempFileName()
$secTemplate | Out-File -FilePath $tempFile -Encoding Unicode
secedit /configure /db secedit.sdb /cfg $tempFile /quiet
Remove-Item $tempFile -Force
# Audit Policy
Write-Host "Configuring audit policies..." -ForegroundColor Cyan
$auditPolicies = @(
"Account Logon:Success,Failure",
"Account Management:Success,Failure",
"Logon/Logoff:Success,Failure",
"Object Access:Success,Failure",
"Policy Change:Success,Failure",
"Privilege Use:Success,Failure",
"System:Success,Failure"
)
foreach ($policy in $auditPolicies) {
$parts = $policy -split ':'
auditpol /set /subcategory:$($parts[0]) /success:enable /failure:enable
}
# Windows Firewall
Write-Host "Configuring Windows Firewall..." -ForegroundColor Cyan
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True
Set-NetFirewallProfile -Profile Public -DefaultInboundAction Block
Set-NetFirewallProfile -Profile Private -DefaultInboundAction Block
# Security Options
Write-Host "Configuring security options..." -ForegroundColor Cyan
# Disable Guest account
Disable-LocalUser -Name "Guest" -ErrorAction SilentlyContinue
# Rename Administrator account
if ($SecurityLevel -in 'Moderate','High') {
Rename-LocalUser -Name "Administrator" -NewName "LocalAdmin" -ErrorAction SilentlyContinue
}
# Registry security settings
$regSettings = @{
# Disable autorun
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer' = @{
'NoDriveTypeAutoRun' = 255
}
# Enable UAC
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System' = @{
'EnableLUA' = 1
'ConsentPromptBehaviorAdmin' = if ($SecurityLevel -eq 'High') { 2 } else { 5 }
'PromptOnSecureDesktop' = 1
}
# Network security
'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters' = @{
'RequireSecuritySignature' = 1
'EnableSecuritySignature' = 1
'RestrictNullSessAccess' = 1
}
}
foreach ($path in $regSettings.Keys) {
if (-not (Test-Path $path)) {
New-Item -Path $path -Force | Out-Null
}
foreach ($name in $regSettings[$path].Keys) {
Set-ItemProperty -Path $path -Name $name -Value $regSettings[$path][$name]
}
}
Write-Host "Security baseline applied successfully!" -ForegroundColor Green
}
function Get-SecurityAssessment {
[CmdletBinding()]
param(
[Parameter(Mandatory=$false)]
[string]$OutputPath = "C:\Reports\SecurityAssessment_$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
)
$assessment = @()
# Check Windows Updates
$updates = Get-HotFix | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1
$daysSinceUpdate = (New-TimeSpan -Start $updates.InstalledOn -End (Get-Date)).Days
$assessment += [PSCustomObject]@{
Category = "Windows Updates"
Check = "Last Update"
Status = if ($daysSinceUpdate -lt 30) { "Pass" } else { "Fail" }
Details = "Last update: $($updates.InstalledOn) ($daysSinceUpdate days ago)"
}
# Check password policy
$passPolicy = Get-ADDefaultDomainPasswordPolicy -ErrorAction SilentlyContinue
if ($passPolicy) {
$assessment += [PSCustomObject]@{
Category = "Password Policy"
Check = "Minimum Length"
Status = if ($passPolicy.MinPasswordLength -ge 12) { "Pass" } else { "Fail" }
Details = "Minimum length: $($passPolicy.MinPasswordLength)"
}
}
# Check firewall status
$fwProfiles = Get-NetFirewallProfile
foreach ($profile in $fwProfiles) {
$assessment += [PSCustomObject]@{
Category = "Firewall"
Check = "$($profile.Name) Profile"
Status = if ($profile.Enabled) { "Pass" } else { "Fail" }
Details = "Enabled: $($profile.Enabled)"
}
}
# Check antivirus status
$av = Get-MpComputerStatus -ErrorAction SilentlyContinue
if ($av) {
$assessment += [PSCustomObject]@{
Category = "Antivirus"
Check = "Real-time Protection"
Status = if ($av.RealTimeProtectionEnabled) { "Pass" } else { "Fail" }
Details = "Enabled: $($av.RealTimeProtectionEnabled)"
}
}
# Generate HTML report
$html = @"
<!DOCTYPE html>
<html>
<head>
<title>Security Assessment Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
.pass { background-color: #4CAF50; color: white; }
.fail { background-color: #f44336; color: white; }
</style>
</head>
<body>
<h1>Security Assessment Report</h1>
<p>Generated: $(Get-Date)</p>
<p>Server: $env:COMPUTERNAME</p>
<table>
<tr>
<th>Category</th>
<th>Check</th>
<th>Status</th>
<th>Details</th>
</tr>
"@
foreach ($item in $assessment) {
$statusClass = if ($item.Status -eq 'Pass') { 'pass' } else { 'fail' }
$html += @"
<tr>
<td>$($item.Category)</td>
<td>$($item.Check)</td>
<td class="$statusClass">$($item.Status)</td>
<td>$($item.Details)</td>
</tr>
"@
}
$html += @"
</table>
<h2>Summary</h2>
<p>Total Checks: $($assessment.Count)</p>
<p>Passed: $(($assessment | Where-Object {$_.Status -eq 'Pass'}).Count)</p>
<p>Failed: $(($assessment | Where-Object {$_.Status -eq 'Fail'}).Count)</p>
</body>
</html>
"@
$html | Out-File -FilePath $OutputPath -Encoding UTF8
Write-Host "Security assessment report generated: $OutputPath" -ForegroundColor Green
# Display summary
$passed = ($assessment | Where-Object {$_.Status -eq 'Pass'}).Count
$failed = ($assessment | Where-Object {$_.Status -eq 'Fail'}).Count
$score = [math]::Round(($passed / $assessment.Count) * 100, 2)
Write-Host "`nSecurity Score: $score%" -ForegroundColor $(if ($score -ge 80) { 'Green' } elseif ($score -ge 60) { 'Yellow' } else { 'Red' })
}
Backup and Recovery Automation
Automated Backup System
# Backup and Recovery Module
function Start-ServerBackup {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string[]]$BackupTargets,
[Parameter(Mandatory=$true)]
[string]$BackupDestination,
[Parameter(Mandatory=$false)]
[ValidateSet('Full','Incremental','Differential')]
[string]$BackupType = 'Full',
[Parameter(Mandatory=$false)]
[switch]$IncludeSystemState,
[Parameter(Mandatory=$false)]
[switch]$IncludeBareMetalRecovery
)
# Install Windows Server Backup if needed
$feature = Get-WindowsFeature -Name Windows-Server-Backup
if ($feature.InstallState -ne 'Installed') {
Install-WindowsFeature -Name Windows-Server-Backup -IncludeManagementTools
}
# Create backup policy
$policy = New-WBPolicy
# Add backup targets
foreach ($target in $BackupTargets) {
if (Test-Path $target) {
$volume = Get-WBVolume -VolumePath $target
Add-WBVolume -Policy $policy -Volume $volume
} else {
Write-Warning "Backup target not found: $target"
}
}
# Add system state if requested
if ($IncludeSystemState) {
Add-WBSystemState -Policy $policy
}
# Add bare metal recovery if requested
if ($IncludeBareMetalRecovery) {
Add-WBBareMetalRecovery -Policy $policy
}
# Set backup destination
if ($BackupDestination -match '^\\\\') {
# Network backup
$backupLocation = New-WBBackupTarget -NetworkPath $BackupDestination
} else {
# Local backup
$backupLocation = New-WBBackupTarget -VolumePath $BackupDestination
}
Add-WBBackupTarget -Policy $policy -Target $backupLocation
# Start backup
Start-WBBackup -Policy $policy
# Log backup details
$logEntry = @{
Timestamp = Get-Date
BackupType = $BackupType
Targets = $BackupTargets -join ';'
Destination = $BackupDestination
SystemState = $IncludeSystemState
BareMetalRecovery = $IncludeBareMetalRecovery
}
$logEntry | Export-Csv -Path "C:\Logs\BackupHistory.csv" -Append -NoTypeInformation
Write-Host "Backup completed successfully" -ForegroundColor Green
}
function New-BackupSchedule {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$TaskName,
[Parameter(Mandatory=$true)]
[string[]]$BackupTargets,
[Parameter(Mandatory=$true)]
[string]$BackupDestination,
[Parameter(Mandatory=$false)]
[string]$Schedule = "Daily",
[Parameter(Mandatory=$false)]
[DateTime]$StartTime = (Get-Date "02:00 AM")
)
# Create scheduled task action
$scriptBlock = @"
Import-Module ServerBackupAutomation
Start-ServerBackup -BackupTargets @('$($BackupTargets -join "','")') -BackupDestination '$BackupDestination' -IncludeSystemState
"@
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -WindowStyle Hidden -Command `"$scriptBlock`""
# Create trigger based on schedule
switch ($Schedule) {
"Daily" {
$trigger = New-ScheduledTaskTrigger -Daily -At $StartTime
}
"Weekly" {
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Wednesday,Friday -At $StartTime
}
"Monthly" {
$trigger = New-ScheduledTaskTrigger -Monthly -DaysOfMonth 1,15 -At $StartTime
}
}
# Set principal
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" `
-LogonType ServiceAccount -RunLevel Highest
# Register task
Register-ScheduledTask -TaskName $TaskName `
-Action $action `
-Trigger $trigger `
-Principal $principal `
-Description "Automated backup task created by PowerShell"
Write-Host "Backup schedule created: $TaskName" -ForegroundColor Green
}
function Test-BackupRecovery {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[string]$BackupLocation,
[Parameter(Mandatory=$true)]
[string]$TestRestorePath
)
Write-Host "Starting backup recovery test..." -ForegroundColor Yellow
# Get latest backup
$backups = Get-WBBackupSet -BackupTarget (Get-WBBackupTarget -VolumePath $BackupLocation)
$latestBackup = $backups | Sort-Object -Property BackupTime -Descending | Select-Object -First 1
if (-not $latestBackup) {
Write-Error "No backups found at $BackupLocation"
return
}
Write-Host "Found backup from: $($latestBackup.BackupTime)" -ForegroundColor Cyan
# Create test restore directory
if (-not (Test-Path $TestRestorePath)) {
New-Item -ItemType Directory -Path $TestRestorePath -Force
}
# Perform test restore of a small portion
try {
$testFiles = Get-WBBackupFile -BackupSet $latestBackup | Select-Object -First 10
foreach ($file in $testFiles) {
Start-WBFileRecovery -BackupSet $latestBackup `
-SourcePath $file.Path `
-TargetPath $TestRestorePath `
-Option CreateSubdirectory `
-Force
}
Write-Host "Recovery test completed successfully" -ForegroundColor Green
# Verify restored files
$restoredFiles = Get-ChildItem -Path $TestRestorePath -Recurse
Write-Host "Restored $($restoredFiles.Count) files for verification" -ForegroundColor Cyan
# Clean up test files
Remove-Item -Path $TestRestorePath -Recurse -Force
} catch {
Write-Error "Recovery test failed: $_"
}
}
Conclusion
PowerShell automation transforms Windows Server 2022 administration from reactive management to proactive orchestration. This comprehensive guide provides the foundation for automating every aspect of server management.
Next Steps
- Implement PowerShell Desired State Configuration (DSC)
- Explore Azure Automation for hybrid environments
- Build custom PowerShell modules for your organization
- Integrate with configuration management tools
- Develop PowerShell-based monitoring solutions
Remember: Automation is about consistency, reliability, and scalability. Start small, test thoroughly, and gradually expand your automation footprint.