Skip to content

TechDirectArchive

Hands-on IT, Cloud, Security, Veeam & DevOps

  • Home
  • About
  • Advertise With US
  • Reviews
  • Contact
  • Toggle search form

Unattended Gold Image & Hardened MSCB & CIS – Windows Server 2025 Image for Veeam Infrastructure

Posted on 25/10/202625/10/2026 Link State By Link State No Comments on Unattended Gold Image & Hardened MSCB & CIS – Windows Server 2025 Image for Veeam Infrastructure
  1. Home
  2. Network | Monitoring
  3. Unattended Gold Image & Hardened MSCB & CIS – Windows Server 2025 Image for Veeam Infrastructure
Unattended Hardened Windows Server 2025
Hardening with Microsoft Security Compliance Baseline 2025 & CIS Standards for Veeam Infrastructure

1. Overview & Architecture: Creating a baseline Golden Image for Windows Server 2025 ensures consistency, security, and rapid deployment across your infrastructure. This guide provides a complete, production-ready workflow to build, harden, and generalize a Windows Server 2025 template specifically optimized for enterprise environments and Veeam Backup & Replication workloads.

2. VMware Virtual Machine Prerequisites

Configure the VMware Template Virtual Machine with the following specifications:

ParameterRecommended Configuration
Operating SystemWindows Server 2025 Standard (64-bit)
FirmwareUEFI Firmware
Security FeaturesSecure Boot Enabled
Virtual HardwareVMware Tools Updated, VMXNET3 NIC, PVSCSI Controller
Virtualization-Based SecurityDisabled (VBS Disabled) (Only for Veeam server)
vCPU / RAM4 vCPU / 16 GB RAM
Hard Disk150 GB (Thin Provisioned)
Template Name Standard[Template]Windows-2025-STD-24H2-ENG-BASE
Unattend_Golden_Image002
Unattend_Golden_Image004
Unattend_Golden_Image005

3. OS Installation & Initial Configuration

During OS setup, select Install Windows Server 2025 Standard (Desktop Experience). Once logged in, execute the following PowerShell commands to standardize locale, test update connectivity, set power management.

Unattend_Golden_Image008

Step 3.1: Regional Settings & Time Zone

# 1. Set Time Zone to Rome (W. Europe Standard Time)
Set-TimeZone -Id “W. Europe Standard Time”

# 2. Set Regional Format to Italian (Italy)
Set-Culture -CultureInfo it-IT

# 3. Set Geographic Location to Italy (GeoID 118)
Set-WinHomeLocation -GeoId 118

Step 3.2: Network Connectivity Test for Windows Update

# Test HTTPS connectivity (Port 443) to primary Windows Update endpoints
$Endpoints = @(
    “windowsupdate.microsoft.com”,
    “update.microsoft.com”,
    “download.windowsupdate.com”,
    “catalog.update.microsoft.com”
)
foreach ($Endpoint in $Endpoints) {
    $Test = Test-NetConnection -ComputerName $Endpoint -Port 443
    [PSCustomObject]@{
        Endpoint = $Endpoint
        TcpTest  = if ($Test.TcpTestSucceeded) { “OK” } else { “FAILED” }
    }
}
Unattend_Golden_Image011a

Step 3.3: Update Scan & Power Optimization

# 1. Initiate update scan via system client and PowerShell module
USOClient StartScan
Get-WindowsUpdate

# 2. Set active power scheme to High Performance
powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

# 3. Verify active power scheme
powercfg /getactivescheme
powercfg /list
Unattend_Golden_Image011

4. Automated Maintenance & Hardening Workflows

To maintain high standards of performance and compliance, run the following automated PowerShell scripts in sequence:

Recommended Order of Execution:
1. Cleanup.ps1:
Purges temp files, Windows Update cache, WER logs, and executes component store cleanup (DISM).
2. OSConfig Baseline.ps1: Audits security compliance against Microsoft Security Compliance 2025 baseline and generates HTML/CSV reports.

Security Analysis of the Scripts

Both scripts are safe and written according to PowerShell best practices, designed for Windows system administrators, particularly for preparing images or Windows Server 2025 servers.

  1. Cleanup.ps1 (Server Cleanup): It is safe. The script uses standard cmdlets (Remove-PathContents, Clear-RecycleBin, DISM) and includes native support for -WhatIf (through [CmdletBinding(SupportsShouldProcess=$true)]), allowing you to simulate execution without deleting any data. The -Aggressive mode also clears Event Logs and forces a DISM component store reset (/ResetBase), making previously installed updates irreversible. This is ideal for creating a golden image, but it should be used with caution.
  2. OSConfig Baseline.ps1 (Inventory and Baseline): It is extremely safe. This is a read-only script that leverages the official Microsoft.OSConfig module to assess security compliance against the Windows Server 2025 Security Baseline. The script exports the results in CSV and HTML formats without modifying any system settings.
  3. The script performs only assessment and reporting activities, making it suitable for security auditing, compliance validation, and baseline verification in production or pre-production environments.

Script 1: System Cleanup (Cleanup.ps1)

The cleanup script prepares the server by removing unnecessary files prior to baseline auditing or cloning:

  • Setup and Logging: Detects the computer name, creates the output directory (C:\Build\Cleanup), and prepares a dual logging system (.log file and .html report).
  • Temp Cleanup: Purges system temporary folders (C:\Windows\Temp) and the current user’s temp directory.
  • Windows Update Cache: Temporarily stops Windows Update services (wuauserv, bits), clears the update download folder (SoftwareDistribution\Download), and restarts the services.
  • Error Reporting (WER): Removes Windows Error Reporting log files.
  • Recycle Bin: Empties the system Recycle Bin.
  • Event Logs (Only with -Aggressive): Clears all Windows Event Logs (wevtutil). If -Aggressive is not specified, this step is skipped.
  • Component Store (DISM): Performs cleanup of unnecessary system components (/StartComponentCleanup) and, when running in -Aggressive mode, applies /ResetBase (irreversible).
  • Final Report: Calculates freed disk space and generates an HTML summary table.

[CmdletBinding(SupportsShouldProcess=$true)]
param(
  [switch]$Aggressive
)

# ------------------- Setup & helpers -------------------
$ErrorActionPreference = 'Continue'
$hostName = (Get-CimInstance Win32_ComputerSystem).Name
$now      = Get-Date -Format 'yyyyMMdd-HHmmss'
$outDir   = 'C:\Build\Cleanup'
New-Item -ItemType Directory -Path $outDir -Force | Out-Null

$logPath  = Join-Path $outDir ("{0}_Cleanup_{1}.log"  -f $hostName,$now)
$htmlPath = Join-Path $outDir ("{0}_Cleanup_{1}.html" -f $hostName,$now)

# Simple logger
function Write-Log {
  param([string]$Message,[string]$Level='INFO')
  $line = "{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
  Add-Content -Path $logPath -Value $line
  Write-Host $line
}

# Byte-size helper
function Get-SizeString([Int64]$bytes) {
  if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) }
  elseif ($bytes -ge 1MB) { '{0:N2} MB' -f ($bytes/1MB) }
  elseif ($bytes -ge 1KB) { '{0:N2} KB' -f ($bytes/1KB) }
  else { "$bytes B" }
}

# Safe remove which returns size deleted
function Remove-PathContents {
  param([string]$Path)
  $deletedBytes = 0
  if (-not (Test-Path $Path)) { return 0 }
  Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | ForEach-Object {
    try {
      $size = 0
      if ($_.PSIsContainer) {
        $size = (Get-ChildItem -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue |
                 Measure-Object -Property Length -Sum).Sum
      } else {
        $size = $_.Length
      }
      if ($PSCmdlet.ShouldProcess($_.FullName,"Remove")) {
        Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
        $deletedBytes += ($size | ForEach-Object { $_ -as [int64] })
      }
    } catch {
      Write-Log "Failed to remove: $($_.FullName) - $($_.Exception.Message)" "WARN"
    }
  }
  return ($deletedBytes | Measure-Object -Sum).Sum
}

# Collect before/after free space for system drive
function Get-FreeSpace {
  (Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace
}

# ------------------- Start -------------------
"==== Server Cleanup on $hostName @ $(Get-Date) ====" | Set-Content $logPath
Write-Log "Aggressive mode: $Aggressive"
Write-Log "Log: $logPath"
Write-Log "HTML: $htmlPath"

$freeBefore = Get-FreeSpace

# ------------------- 1) Temp files -------------------
Write-Log "1) Clearing Temp folders (System & Current User)..."
$sysTemp = Join-Path $env:SystemRoot 'Temp'
$usrTemp = $env:TEMP

$sizeSys = Remove-PathContents -Path $sysTemp
$sizeUsr = Remove-PathContents -Path $usrTemp

Write-Log (" - System Temp: removed {0}" -f (Get-SizeString $sizeSys))
Write-Log (" - User Temp  : removed {0}" -f (Get-SizeString $sizeUsr))

# ------------------- 2) Windows Update cache -------------------
Write-Log "2) Clearing Windows Update cache (SoftwareDistribution\Download)..."
try {
  if ($PSCmdlet.ShouldProcess("wuauserv/bits","Stop-Service")) {
    Stop-Service wuauserv -Force -ErrorAction SilentlyContinue
    Stop-Service bits -Force -ErrorAction SilentlyContinue
  }
  $wuPath = Join-Path $env:SystemRoot 'SoftwareDistribution\Download'
  $sizeWU = Remove-PathContents -Path $wuPath
  Write-Log (" - WU Cache: removed {0}" -f (Get-SizeString $sizeWU))
} finally {
  if ($PSCmdlet.ShouldProcess("wuauserv/bits","Start-Service")) {
    Start-Service wuauserv -ErrorAction SilentlyContinue
    Start-Service bits -ErrorAction SilentlyContinue
  }
}

# ------------------- 3) Windows Error Reporting -------------------
Write-Log "3) Clearing Windows Error Reporting (WER) queue..."
$werPath = Join-Path $env:ProgramData 'Microsoft\Windows\WER'
$sizeWER = Remove-PathContents -Path $werPath
Write-Log (" - WER: removed {0}" -f (Get-SizeString $sizeWER))

# ------------------- 4) Recycle Bin -------------------
Write-Log "4) Emptying Recycle Bin..."
try {
  if ($PSCmdlet.ShouldProcess("RecycleBin","Clear")) {
    Clear-RecycleBin -Force -ErrorAction SilentlyContinue
    Write-Log " - Recycle Bin cleared"
  }
} catch {
  Write-Log " - Recycle Bin clear failed: $($_.Exception.Message)" "WARN"
}

# ------------------- 5) Event Logs (Aggressive only) -------------------
$totalLogsCleared = 0
if ($Aggressive) {
  Write-Log "5) Clearing ALL Event Logs (Aggressive)..."
  try {
    $logs = wevtutil el
    foreach ($log in $logs) {
      if ($PSCmdlet.ShouldProcess($log,"wevtutil cl")) {
        & wevtutil cl "$log" 2>$null
        $totalLogsCleared++
      }
    }
    Write-Log " - Cleared $totalLogsCleared logs"
  } catch {
    Write-Log " - Event log clear encountered errors: $($_.Exception.Message)" "WARN"
  }
} else {
  Write-Log "5) Event Logs: skipped (run with -Aggressive to clear all)"
}

# ------------------- 6) Component Store cleanup -------------------
Write-Log "6) Component Store cleanup (DISM /StartComponentCleanup)..."
try {
  if ($PSCmdlet.ShouldProcess("DISM","/Online /Cleanup-Image /StartComponentCleanup")) {
    DISM /Online /Cleanup-Image /StartComponentCleanup | Out-Null
    Write-Log " - StartComponentCleanup completed"
  }
  if ($Aggressive) {
    Write-Log " - Aggressive: DISM /ResetBase (irreversible)"
    if ($PSCmdlet.ShouldProcess("DISM","/Online /Cleanup-Image /StartComponentCleanup /ResetBase")) {
      DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null
      Write-Log " - ResetBase completed"
    }
  } else {
    Write-Log " - ResetBase skipped (use -Aggressive to enable)"
  }
} catch {
  Write-Log " - DISM cleanup error: $($_.Exception.Message)" "WARN"
}

# ------------------- 7) Optional Disk Cleanup profile hook -------------------
# Uncomment to run a preconfigured CleanMgr profile silently:
# Write-Log "7) Running CleanMgr /sagerun:1 ..."
# if ($PSCmdlet.ShouldProcess("CleanMgr","/sagerun:1")) { Start-Process -FilePath cleanmgr.exe -ArgumentList '/sagerun:1' -Wait -WindowStyle Hidden }

# ------------------- Summary -------------------
$freeAfter = Get-FreeSpace
$freed = ($freeAfter - $freeBefore)
Write-Log ("==== DONE. Freed approx {0}. System free space now: {1}" -f (Get-SizeString $freed),(Get-SizeString $freeAfter))

# ------------------- HTML Summary -------------------
$style = @"
<style>
 body{font-family:Segoe UI,Arial,sans-serif;margin:20px}
 table{border-collapse:collapse;width:100%}
 th,td{border:1px solid #ddd;padding:6px 8px;font-size:12px;text-align:left}
 th{background:#f4f4f4}
</style>
"@

$rows = @(
  @{Name='Host';Value=$hostName},
  @{Name='Time';Value=(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')},
  @{Name='Aggressive';Value=$Aggressive},
  @{Name='Temp (System)';Value=(Get-SizeString $sizeSys)},
  @{Name='Temp (User)';Value=(Get-SizeString $sizeUsr)},
  @{Name='WU Cache';Value=(Get-SizeString $sizeWU)},
  @{Name='WER';Value=(Get-SizeString $sizeWER)},
  @{Name='Event Logs Cleared';Value= if($Aggressive){$totalLogsCleared}else{'Skipped'}},
  @{Name='Freed Space';Value=(Get-SizeString $freed)},
  @{Name='Free Space Now';Value=(Get-SizeString $freeAfter)},
  @{Name='Log File';Value=$logPath}
) | ForEach-Object {
  "<tr><th>$($_.Name)</th><td>$($_.Value)</td></tr>"
} | Out-String

$html = $style + @"
<h2>Server First-Cleanup Summary – $hostName</h2>
<table>$rows</table>
<p>Detailed log: <code>$logPath</code></p>
"@

Set-Content -Path $htmlPath -Value $html -Encoding UTF8

Write-Host "`nLog  : $logPath"
Write-Host "HTML : $htmlPath"
Write-Host "Tip  : Consider a reboot, then snapshot or sysprep your image." -ForegroundColor Cyan 
# Execute aggressive cleanup prior to image creation
.\02-Cleanup.ps1 -Aggressive
Unattend_Golden_Image015

Script 2: Baseline Audit (Microsoft.OSConfig)

The Microsoft.OSConfig module is the official solution for managing security baselines in Windows Server 2025.

Firewall Prerequisites for Online Module Installation: Ensure outbound access over HTTPS TCP 443 is permitted to: *.powershellgallery.com, cdn.powershellgallery.com, cdn.oneget.org, go.microsoft.com, aka.ms.

# Online Installation from PSGallery
Install-Module -Name Microsoft.OSConfig -Scope AllUsers -Repository PSGallery -Force
Get-Module -ListAvailable -Name Microsoft.OSConfig
Unattend_Golden_Image019
Unattend_Golden_Image021
# ========= Windows Server 2025 OSConfig Inventory Export =========
# Run in an elevated PowerShell session.
#Installare il modulo Microsoft.OSConfig
# Install-Module -Name Microsoft.OSConfig -Scope AllUsers -Repository PSGallery -Force
# Get-Module -ListAvailable -Name Microsoft.OSConfig
#Se la macchina non ha accesso a Internet (Ambiente Offline / Air-Gapped)
#Se il server è isolato e non può raggiungere la rete esterna, dovrai scaricare il modulo da un computer connesso a Internet tramite il comando:
#Save-Module -Name Microsoft.OSConfig -Path C:\Temp
#Get-Module -ListAvailable -Name Microsoft.OSConfig
$Scenario = 'SecurityBaseline/WindowsServer/2025/WorkgroupMember'
$Now = Get-Date -Format 'yyyyMMdd-HHmmss'
$OutDir = 'C:\Build\Inventory'
New-Item -ItemType Directory -Path $OutDir -Force | Out-Null
# (Optional) ensure OSConfig module is available
if (-not (Get-Command Get-OSConfigDesiredConfiguration -ErrorAction SilentlyContinue)) {
Write-Error "OSConfig cmdlets not found. Install with: Install-Module Microsoft.OSConfig -Scope AllUsers"
return
}
# Get system info for filenames and HTML header
$os = Get-CimInstance Win32_OperatingSystem
$comp = Get-CimInstance Win32_ComputerSystem
$ServerName = $comp.Name # Hostname
$Header = @"
<h2>Windows Server 2025 – Member Server Baseline Inventory</h2>
<p>
<b>Computer:</b> $($comp.Name)<br/>
<b>Domain:</b> $($comp.Domain)<br/>
<b>OS:</b> $($os.Caption) ($($os.Version)) Build $($os.BuildNumber)<br/>
<b>Scenario:</b> $Scenario<br/>
<b>Generated:</b> $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')
</p>
"@
# Pull current desired configuration with compliance status
$data = Get-OSConfigDesiredConfiguration -Scenario $Scenario
if (-not $data) {
Write-Warning "No OSConfig data returned for '$Scenario'. Is the baseline applied on this server?"
# Still produce a minimal HTML so you have an artifact:
$HtmlPath = Join-Path $OutDir ("{0}_WS2025_MemberServer_Inventory_{1}.html" -f $ServerName,$Now)
$Minimal = "<p>No data returned for scenario '$Scenario'.</p>"
$Style = "<style>body{font-family:Segoe UI,Arial,sans-serif;margin:20px}</style>"
Set-Content -Path $HtmlPath -Value ($Style + $Header + $Minimal) -Encoding UTF8
Write-Host "HTML : $HtmlPath"
return
}
# Shape data safely (avoid null member errors)
$report = $data | Select-Object Name, Value,
@{n='Status'; e={ if ($_.Compliance) { $_.Compliance.Status } else { $null } }},
@{n='Reason'; e={ if ($_.Compliance) { $_.Compliance.Reason } else { $null } }}
# --- CSV export ---
$CsvPath = Join-Path $OutDir ("{0}_WS2025_MemberServer_Inventory_{1}.csv" -f $ServerName,$Now)
$report | Sort-Object Name | Export-Csv $CsvPath -NoTypeInformation -Encoding UTF8
# --- HTML export (color-coded by Status) ---
$HtmlPath = Join-Path $OutDir ("{0}_WS2025_MemberServer_Inventory_{1}.html" -f $ServerName,$Now)
$Style = @"
<style>
body { font-family: Segoe UI, Arial, sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 6px 8px; font-size: 12px; }
th { background: #f4f4f4; text-align: left; }
.ok { background-color: #e6ffed; } /* Compliant */
.warn { background-color: #fff8e1; } /* Unknown/Other */
.err { background-color: #ffecec; } /* NonCompliant */
.meta { margin-bottom: 14px; }
</style>
"@
$Post = @"
<script>
(function(){
const rows = document.querySelectorAll('table tbody tr');
for (const tr of rows) {
const status = (tr.cells[2]?.innerText || '').trim();
if (status === 'Compliant') tr.classList.add('ok');
else if (status === 'NonCompliant') tr.classList.add('err');
else tr.classList.add('warn');
}
})();
</script>
"@
$HtmlTable = $report |
Sort-Object Name |
ConvertTo-Html -Title 'WS2025 Member Server Baseline Inventory' `
-PreContent ("<div class='meta'>{0}</div>" -f $Header) `
-PostContent $Post
Set-Content -Path $HtmlPath -Value ($Style + $HtmlTable) -Encoding UTF8
Write-Host "CSV : $CsvPath"
Write-Host "HTML : $HtmlPath"
# ================================================================

5. Offline Security Baseline Deployment Procedure

For offline or air-gapped environments without direct internet connectivity, deploy the OSConfig module manually:

Step 1: Download & Transfer Module

# Execute on an internet-connected computer:
Save-Module -Name Microsoft.OSConfig -Path C:\Temp

# Copy the saved module folder to the target server’s module directory:
# C:\Program Files\WindowsPowerShell\Modules\Microsoft.OSConfig\
# Verify Microsoft.OSConfig.psd1 exists in the version subfolder.
Unattend_Golden_Image025

Copy Dowloaded module in

Unattend_Golden_Image027
Unattend_Golden_Image029

Step 2: Import & Verify Module Loading

Ensure that the Microsoft.OSConfig module folder is placed correctly in the server’s PowerShell modules directory: C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Microsoft.OSConfig\

# Verify Module Path
$env:PSModulePath
# Force import OSConfig module
Import-Module Microsoft.OSConfig -Force
# Confirm module loaded successfully
Get-Module Microsoft.OSConfig
Get-Command -Module Microsoft.OSConfig
Unattend_Golden_Image031

Verification: The folder must contain the manifest file with the .psd1 extension.

Unattend_Golden_Image033
# Force import OSConfig module
Import-Module Microsoft.OSConfig -Force
Unattend_Golden_Image035

Verification: Run Get-Module Microsoft.OSConfig to confirm that the module is correctly loaded into memory.

Unattend_Golden_Image037
# Confirm module loaded successfully
Get-Module Microsoft.OSConfig
Get-Command -Module Microsoft.OSConfig
Unattend_Golden_Image039

Step 3: Apply Baseline & Audit Compliance

# Apply recommended Windows Server 2025 Workgroup Security Baseline
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember -Default
# View detailed compliance status across all applied rules
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember
# Count total configured settings (approx. 319 rules)
(Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember | Measure-Object).Count
# Generate a formatted list of rule compliance statuses and reasons
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember |
  ft Name, @{Name="Status";Expression={$_.Compliance.Status}}, @{Name="Reason";Expression={$_.Compliance.Reason}} -AutoSize -Wrap
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember -Default
Unattend_Golden_Image041
# View detailed compliance status across all applied rules
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember
Unattend_Golden_Image043
(Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember | Measure-Object).Count
Unattend_Golden_Image045
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember |
ft Name, @{Name="Status";Expression={$_.Compliance.Status}}, @{Name="Reason";Expression={$_.Compliance.Reason}} -AutoSize -Wrap
Unattend_Golden_Image047

6. Post-Baseline Infrastructure Fine-Tuning

Depending on operational requirements, adjust individual baseline settings while preserving core security integrity:

Remote Desktop Services (RDP Drive Redirection)

RDP Quality-of-Life

# Enable RDP Local Drive Redirection (Set value to 0)
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WS2025/MemberServer `
  -Name RemoteDesktopServicesDoNotAllowDriveRedirection -Value 0
Restart-Computer
Security Note — Production & Critical Servers:
Enabling drive redirection marks the server as ‘Non-Compliant’ against Microsoft baselines. In production environments, keep drive redirection disabled to mitigate malware transmission and data exfiltration risks.

Advanced Auditing Settings

# Enable Success and Failure logging for Detailed File Share Access
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WS2025/MemberServer `
  -Name AuditDetailedFileShare -Value 3
Restart-Computer

Security & Compliance Impact: Provides forensic visibility (Event IDs 5140, 5145) to meet compliance requirements (e.g. NIST, ISO 27001). Increase max Security log size on high-throughput file servers to avoid log wrapping.

Defender Antivirus & AppControl (Veeam Infrastructure)

# Apply Defender Antivirus & AppControl Audit Mode Baselines
Set-OSConfigDesiredConfiguration -Scenario Defender/Antivirus -Default
Set-OSConfigDesiredConfiguration -Scenario AppControl/WS2025/DefaultPolicy/Audit -Default
# Verify Effective AppControl Policies
(Get-OSConfigDesiredConfiguration -Scenario AppControl).Value.PolicyInfo | Where-Object {$_.IsEffective -eq $true }
Get-OSConfigDesiredConfiguration -Scenario AppControl
Unattend_Golden_Image049

Set-OSConfigDesiredConfiguration -Scenario AppControl/WS2025/DefaultPolicy/Audit -Default
Unattend_Golden_Image051
Veeam Infrastructure Considerations:
• Microsoft Defender:
Real-time scanning can significantly degrade backup performance. Configure mandatory exclusions for repository paths, proxy cache folders, and Veeam executables per official Veeam Best Practices.
KB1999: Antivirus Exclusions for Veeam Backup & Replication
• AppControl (Audit Mode): Logs unapproved binary execution attempts without blocking, allowing validation of Veeam components prior to block mode enforcement.
################### Antivirus Exclusions for Veeam Backup & Replication #############
############################### VEEAM BACKUP SERVER ###############################
#Change your drive letters if need
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\VeeamFLR\"
Add-MpPreference -ExclusionPath "C:\Windows\Veeam\"
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\Backup and Replication\Backup Catalog\"
Add-MpPreference -ExclusionPath "E:\vPowerNFS\NfsDatastore"
#VBRCATALOG PATH
#This path can be found in the registry under the value named CatalogPath in the key
HKLM\SOFTWARE\Veeam\Veeam Backup Catalog\
#NFS PATH
#This path can be found in the registry under the value named RootFolder in the key
HKLM\SOFTWARE\Wow6432Node\Veeam\Veeam NFS\
############################### THIS IS THE DEFAULT LOG DIRECTORY LOCATION, IF THE LOG DIRECTORY PATH HAS BEEN CHANGED, THE AV EXCLUSION MUST BE ADJUSTED TO MATCH.###############################
Add-MpPreference -ExclusionPath "C:\Windows\Temp\*\veeamflr-*.flat"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackup\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackupTemp\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\veeamdumprecorder\"
#This is a per-user folder.
Add-MpPreference -ExclusionPath "%localappdata%\Veeam\Backup\"
############################### VEEAM BACKUP ENTERPRISE MANAGER ###############################
#VBRCatalog Path
#This path can be found in the registry under the value named CatalogPath in the key HKLM\SOFTWARE\Veeam\Veeam Backup Catalog\
#This is the default log directory location; if the log directory path has been changed, the AV exclusion must be adjusted to match.
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\VBRCatalog"
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
############################### VMWARE BACKUP PROXY, WAN ACCELERATOR, WINDOWS REPOSITORY ###############################
#General Folders
#All Windows-based components use the following folders:
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\Veeam\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackupTemp\"
#Instan recovery path
Add-MpPreference -ExclusionPath E:\ProgramData\Veeam\Backup\IRCache\
########################## PACKAGE SPECIFIC AV EXCLUSIONS ###############################
Veeam Installer Service Add-MpPreference -ExclusionPath "C:\Windows\Veeam\Backup\"
Veeam Backup Transport Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\Backup Transport\"
Veeam Backup vPowerNFS Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\vPowerNFS\"
Veeam CDP Proxy Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\CDP Proxy Service\"
Veeam CDP Proxy Cache Folder Add-MpPreference -ExclusionPath "C:\VeeamCDP\"
############################### HYPERV ###############################
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\vPowerNFS\"
#Instant recover write cache folder
#Review each repository's Mount Server setting and add an AV exclusion for the write cache path on the Mount Server specified.
Add-MpPreference -ExclusionPath E:\ProgramData\Veeam\Backup\IRCache\
## Hyper-V Integration
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\Hyper-V Integration\"
############################### VEEAM MOUNT SERVICE ###############################
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\Backup and Replication\"
Add-MpPreference -ExclusionPath "C:\VeeamFLR\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\*\veeamflr-*.flat"
#WAN
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\WAN Accelerator Service\"
# Tape
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\Backup Tape\"
GATEWAY
Add-MpPreference -ExclusionPath " C:\Program Files (x86)\Veeam\Backup Gate\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\Backup and Replication\Log Backup Service\"
############################### REPOSITORY FILE EXTENSIONS ###############################
Add-MpPreference -ExclusionExtension
Add-MpPreference -ExclusionExtension *.erm
Add-MpPreference -ExclusionExtension *.flat
Add-MpPreference -ExclusionExtension *.vab
Add-MpPreference -ExclusionExtension *.vbk
Add-MpPreference -ExclusionExtension *.vbk.tmp
Add-MpPreference -ExclusionExtension *.vblob
Add-MpPreference -ExclusionExtension *.vbm
Add-MpPreference -ExclusionExtension *.vbm_*tmp
Add-MpPreference -ExclusionExtension *.vbm.temp
Add-MpPreference -ExclusionExtension *.vcache
Add-MpPreference -ExclusionExtension *.vib
Add-MpPreference -ExclusionExtension *.vindex
Add-MpPreference -ExclusionExtension *.vlb
Add-MpPreference -ExclusionExtension *.vmdk
Add-MpPreference -ExclusionExtension *.vrb
Add-MpPreference -ExclusionExtension *.vsb
Add-MpPreference -ExclusionExtension *.vslice
Add-MpPreference -ExclusionExtension *.vsource
Add-MpPreference -ExclusionExtension *.vsourcecopy
Add-MpPreference -ExclusionExtension *.vsourcetemp
Add-MpPreference -ExclusionExtension *.vstore
Add-MpPreference -ExclusionExtension *.vstorecopy
Add-MpPreference -ExclusionExtension *.vstoretemp

AppControl in Audit Mode (Scenario AppControl/…/Audit)

  • Impact: Minimal. Being in Audit mode, the system only logs executions that do not comply with the policy without blocking anything. The performance impact is negligible, but it serves to verify that Veeam’s proprietary drivers or binaries are not flagged as anomalous prior to enforcing a full block policy.

7. Drift Control & Unattended Sysprep Packaging

Configuring Drift Control

Important Configuration Requirement:
Apply this setting only at the end of the deployment and server configuration process.
# Enable OSConfig Drift Control (Enforce baseline every 45 minutes)
Set-OSConfigDriftControl 45

Final Sysprep & Unattend Execution

Automate Windows Server 2025 Installation with Unattend.xml

Follow these final steps to seal and generalize the Golden Image template:
1. Remove unnecessary temporary files, software, and domain associations.
2. Perform a final Windows Update cycle and reboot.
3. Shutdown the virtual machine and take a VMware Snapshot (Pre-Sysprep).
4. Confirm SetupComplete.bat exists in C:\Windows\Setup\Scripts\ to clean unattend files post-OOBE:

@echo off del /Q /F C:\Windows\System32\Sysprep\unattend.xml
del /Q /F C:\Windows\Panther\unattend.xml
del /Q /F C:\Windows\Panther\unattend\unattend.xml
del /Q /F C:\Unattend.xml exit /b 0

5. Execute the final generalization command in PowerShell / CMD:

.\sysprep.exe /generalize /oobe /shutdown /unattend:c:\windows\system32\sysprep\unattend.xml
Unattend_Golden_Image059
Unattend_Golden_Image061

Convert the VMware VM into a template and deploy a new VM from the template to verify that the golden image is functioning as expected.

Thank you for reading. I hope this guide helps you successfully implement your solution. If you have any questions or suggestions, feel free to leave a comment below.

Hardening with Microsoft Security Compliance Baseline 2025 & CIS Standards for Veeam Infrastructure

Unattended Hardened Windows Server 2025

References:

Configure Security Baselines for Windows Server 2025 | Microsoft Learn

Windows Server 2025, security baseline | Microsoft Community Hub

osconfig/security at main · microsoft/osconfig · GitHub

1. Overview & Architecture

Creating a baseline Golden Image for Windows Server 2025 ensures consistency, security, and rapid deployment across your infrastructure. This guide provides a complete, production-ready workflow to build, harden, and generalize a Windows Server 2025 template specifically optimized for enterprise environments and Veeam Backup & Replication workloads.

2. VMware Virtual Machine Prerequisites

Configure the VMware Template Virtual Machine with the following specifications:

ParameterRecommended Configuration
Operating SystemWindows Server 2025 Standard (64-bit)
FirmwareUEFI Firmware
Security FeaturesSecure Boot Enabled
Virtual HardwareVMware Tools Updated, VMXNET3 NIC, PVSCSI Controller
Virtualization-Based SecurityDisabled (VBS Disabled) (Only for Veeam server)
vCPU / RAM4 vCPU / 16 GB RAM
Hard Disk150 GB (Thin Provisioned)
Template Name Standard[Template]Windows-2025-STD-24H2-ENG-BASE
Unattend_Golden_Image002
Unattend_Golden_Image004
Unattend_Golden_Image005

3. OS Installation & Initial Configuration

During OS setup, select Install Windows Server 2025 Standard (Desktop Experience). Once logged in, execute the following PowerShell commands to standardize locale, test update connectivity, set power management.

Unattend_Golden_Image008

Step 3.1: Regional Settings & Time Zone

# 1. Set Time Zone to Rome (W. Europe Standard Time)
Set-TimeZone -Id “W. Europe Standard Time”

# 2. Set Regional Format to Italian (Italy)
Set-Culture -CultureInfo it-IT

# 3. Set Geographic Location to Italy (GeoID 118)
Set-WinHomeLocation -GeoId 118

Step 3.2: Network Connectivity Test for Windows Update

# Test HTTPS connectivity (Port 443) to primary Windows Update endpoints
$Endpoints = @(
    “windowsupdate.microsoft.com”,
    “update.microsoft.com”,
    “download.windowsupdate.com”,
    “catalog.update.microsoft.com”
)
foreach ($Endpoint in $Endpoints) {
    $Test = Test-NetConnection -ComputerName $Endpoint -Port 443
    [PSCustomObject]@{
        Endpoint = $Endpoint
        TcpTest  = if ($Test.TcpTestSucceeded) { “OK” } else { “FAILED” }
    }
}
Unattend_Golden_Image011a

Step 3.3: Update Scan & Power Optimization

# 1. Initiate update scan via system client and PowerShell module
USOClient StartScan
Get-WindowsUpdate

# 2. Set active power scheme to High Performance
powercfg -setactive 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c

# 3. Verify active power scheme
powercfg /getactivescheme
powercfg /list
Unattend_Golden_Image011

4. Automated Maintenance & Hardening Workflows

To maintain high standards of performance and compliance, run the following automated PowerShell scripts in sequence:

Recommended Order of Execution:
1. Cleanup.ps1:
Purges temp files, Windows Update cache, WER logs, and executes component store cleanup (DISM).
2. OSConfig Baseline.ps1: Audits security compliance against Microsoft Security Compliance 2025 baseline and generates HTML/CSV reports.

Security Analysis of the Scripts

Both scripts are safe and written according to PowerShell best practices, designed for Windows system administrators, particularly for preparing images or Windows Server 2025 servers.

  1. Cleanup.ps1 (Server Cleanup):
    It is safe. The script uses standard cmdlets (Remove-PathContents, Clear-RecycleBin, DISM) and includes native support for -WhatIf (through [CmdletBinding(SupportsShouldProcess=$true)]), allowing you to simulate execution without deleting any data. The -Aggressive mode also clears Event Logs and forces a DISM component store reset (/ResetBase), making previously installed updates irreversible. This is ideal for creating a golden image, but it should be used with caution.
  2. OSConfig Baseline.ps1 (Inventory and Baseline):
    It is extremely safe. This is a read-only script that leverages the official Microsoft.OSConfig module to assess security compliance against the Windows Server 2025 Security Baseline. The script exports the results in CSV and HTML formats without modifying any system settings.
  3. The script performs only assessment and reporting activities, making it suitable for security auditing, compliance validation, and baseline verification in production or pre-production environments.

Script 1: System Cleanup (Cleanup.ps1)

The cleanup script prepares the server by removing unnecessary files prior to baseline auditing or cloning:
Setup and Logging:
Detects the computer name, creates the output directory (C:\Build\Cleanup), and prepares a dual logging system (.log file and .html report).

Temp Cleanup: Purges system temporary folders (C:\Windows\Temp) and the current user’s temp directory.

Windows Update Cache: Temporarily stops Windows Update services (wuauserv, bits), clears the update download folder (SoftwareDistribution\Download), and restarts the services.

Error Reporting (WER): Removes Windows Error Reporting log files.

Recycle Bin: Empties the system Recycle Bin.

Event Logs (Only with -Aggressive): Clears all Windows Event Logs (wevtutil). If -Aggressive is not specified, this step is skipped.

Component Store (DISM): Performs cleanup of unnecessary system components (/StartComponentCleanup) and, when running in -Aggressive mode, applies /ResetBase (irreversible).

Final Report: Calculates freed disk space and generates an HTML summary table.


[CmdletBinding(SupportsShouldProcess=$true)]
param(
  [switch]$Aggressive
)

# ------------------- Setup & helpers -------------------
$ErrorActionPreference = 'Continue'
$hostName = (Get-CimInstance Win32_ComputerSystem).Name
$now      = Get-Date -Format 'yyyyMMdd-HHmmss'
$outDir   = 'C:\Build\Cleanup'
New-Item -ItemType Directory -Path $outDir -Force | Out-Null

$logPath  = Join-Path $outDir ("{0}_Cleanup_{1}.log"  -f $hostName,$now)
$htmlPath = Join-Path $outDir ("{0}_Cleanup_{1}.html" -f $hostName,$now)

# Simple logger
function Write-Log {
  param([string]$Message,[string]$Level='INFO')
  $line = "{0} [{1}] {2}" -f (Get-Date -Format 'yyyy-MM-dd HH:mm:ss'), $Level, $Message
  Add-Content -Path $logPath -Value $line
  Write-Host $line
}

# Byte-size helper
function Get-SizeString([Int64]$bytes) {
  if ($bytes -ge 1GB) { '{0:N2} GB' -f ($bytes/1GB) }
  elseif ($bytes -ge 1MB) { '{0:N2} MB' -f ($bytes/1MB) }
  elseif ($bytes -ge 1KB) { '{0:N2} KB' -f ($bytes/1KB) }
  else { "$bytes B" }
}

# Safe remove which returns size deleted
function Remove-PathContents {
  param([string]$Path)
  $deletedBytes = 0
  if (-not (Test-Path $Path)) { return 0 }
  Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | ForEach-Object {
    try {
      $size = 0
      if ($_.PSIsContainer) {
        $size = (Get-ChildItem -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue |
                 Measure-Object -Property Length -Sum).Sum
      } else {
        $size = $_.Length
      }
      if ($PSCmdlet.ShouldProcess($_.FullName,"Remove")) {
        Remove-Item -LiteralPath $_.FullName -Recurse -Force -ErrorAction SilentlyContinue
        $deletedBytes += ($size | ForEach-Object { $_ -as [int64] })
      }
    } catch {
      Write-Log "Failed to remove: $($_.FullName) - $($_.Exception.Message)" "WARN"
    }
  }
  return ($deletedBytes | Measure-Object -Sum).Sum
}

# Collect before/after free space for system drive
function Get-FreeSpace {
  (Get-CimInstance Win32_LogicalDisk -Filter "DeviceID='C:'").FreeSpace
}

# ------------------- Start -------------------
"==== Server Cleanup on $hostName @ $(Get-Date) ====" | Set-Content $logPath
Write-Log "Aggressive mode: $Aggressive"
Write-Log "Log: $logPath"
Write-Log "HTML: $htmlPath"

$freeBefore = Get-FreeSpace

# ------------------- 1) Temp files -------------------
Write-Log "1) Clearing Temp folders (System & Current User)..."
$sysTemp = Join-Path $env:SystemRoot 'Temp'
$usrTemp = $env:TEMP

$sizeSys = Remove-PathContents -Path $sysTemp
$sizeUsr = Remove-PathContents -Path $usrTemp

Write-Log (" - System Temp: removed {0}" -f (Get-SizeString $sizeSys))
Write-Log (" - User Temp  : removed {0}" -f (Get-SizeString $sizeUsr))

# ------------------- 2) Windows Update cache -------------------
Write-Log "2) Clearing Windows Update cache (SoftwareDistribution\Download)..."
try {
  if ($PSCmdlet.ShouldProcess("wuauserv/bits","Stop-Service")) {
    Stop-Service wuauserv -Force -ErrorAction SilentlyContinue
    Stop-Service bits -Force -ErrorAction SilentlyContinue
  }
  $wuPath = Join-Path $env:SystemRoot 'SoftwareDistribution\Download'
  $sizeWU = Remove-PathContents -Path $wuPath
  Write-Log (" - WU Cache: removed {0}" -f (Get-SizeString $sizeWU))
} finally {
  if ($PSCmdlet.ShouldProcess("wuauserv/bits","Start-Service")) {
    Start-Service wuauserv -ErrorAction SilentlyContinue
    Start-Service bits -ErrorAction SilentlyContinue
  }
}

# ------------------- 3) Windows Error Reporting -------------------
Write-Log "3) Clearing Windows Error Reporting (WER) queue..."
$werPath = Join-Path $env:ProgramData 'Microsoft\Windows\WER'
$sizeWER = Remove-PathContents -Path $werPath
Write-Log (" - WER: removed {0}" -f (Get-SizeString $sizeWER))

# ------------------- 4) Recycle Bin -------------------
Write-Log "4) Emptying Recycle Bin..."
try {
  if ($PSCmdlet.ShouldProcess("RecycleBin","Clear")) {
    Clear-RecycleBin -Force -ErrorAction SilentlyContinue
    Write-Log " - Recycle Bin cleared"
  }
} catch {
  Write-Log " - Recycle Bin clear failed: $($_.Exception.Message)" "WARN"
}

# ------------------- 5) Event Logs (Aggressive only) -------------------
$totalLogsCleared = 0
if ($Aggressive) {
  Write-Log "5) Clearing ALL Event Logs (Aggressive)..."
  try {
    $logs = wevtutil el
    foreach ($log in $logs) {
      if ($PSCmdlet.ShouldProcess($log,"wevtutil cl")) {
        & wevtutil cl "$log" 2>$null
        $totalLogsCleared++
      }
    }
    Write-Log " - Cleared $totalLogsCleared logs"
  } catch {
    Write-Log " - Event log clear encountered errors: $($_.Exception.Message)" "WARN"
  }
} else {
  Write-Log "5) Event Logs: skipped (run with -Aggressive to clear all)"
}

# ------------------- 6) Component Store cleanup -------------------
Write-Log "6) Component Store cleanup (DISM /StartComponentCleanup)..."
try {
  if ($PSCmdlet.ShouldProcess("DISM","/Online /Cleanup-Image /StartComponentCleanup")) {
    DISM /Online /Cleanup-Image /StartComponentCleanup | Out-Null
    Write-Log " - StartComponentCleanup completed"
  }
  if ($Aggressive) {
    Write-Log " - Aggressive: DISM /ResetBase (irreversible)"
    if ($PSCmdlet.ShouldProcess("DISM","/Online /Cleanup-Image /StartComponentCleanup /ResetBase")) {
      DISM /Online /Cleanup-Image /StartComponentCleanup /ResetBase | Out-Null
      Write-Log " - ResetBase completed"
    }
  } else {
    Write-Log " - ResetBase skipped (use -Aggressive to enable)"
  }
} catch {
  Write-Log " - DISM cleanup error: $($_.Exception.Message)" "WARN"
}

# ------------------- 7) Optional Disk Cleanup profile hook -------------------
# Uncomment to run a preconfigured CleanMgr profile silently:
# Write-Log "7) Running CleanMgr /sagerun:1 ..."
# if ($PSCmdlet.ShouldProcess("CleanMgr","/sagerun:1")) { Start-Process -FilePath cleanmgr.exe -ArgumentList '/sagerun:1' -Wait -WindowStyle Hidden }

# ------------------- Summary -------------------
$freeAfter = Get-FreeSpace
$freed = ($freeAfter - $freeBefore)
Write-Log ("==== DONE. Freed approx {0}. System free space now: {1}" -f (Get-SizeString $freed),(Get-SizeString $freeAfter))

# ------------------- HTML Summary -------------------
$style = @"
<style>
 body{font-family:Segoe UI,Arial,sans-serif;margin:20px}
 table{border-collapse:collapse;width:100%}
 th,td{border:1px solid #ddd;padding:6px 8px;font-size:12px;text-align:left}
 th{background:#f4f4f4}
</style>
"@

$rows = @(
  @{Name='Host';Value=$hostName},
  @{Name='Time';Value=(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')},
  @{Name='Aggressive';Value=$Aggressive},
  @{Name='Temp (System)';Value=(Get-SizeString $sizeSys)},
  @{Name='Temp (User)';Value=(Get-SizeString $sizeUsr)},
  @{Name='WU Cache';Value=(Get-SizeString $sizeWU)},
  @{Name='WER';Value=(Get-SizeString $sizeWER)},
  @{Name='Event Logs Cleared';Value= if($Aggressive){$totalLogsCleared}else{'Skipped'}},
  @{Name='Freed Space';Value=(Get-SizeString $freed)},
  @{Name='Free Space Now';Value=(Get-SizeString $freeAfter)},
  @{Name='Log File';Value=$logPath}
) | ForEach-Object {
  "<tr><th>$($_.Name)</th><td>$($_.Value)</td></tr>"
} | Out-String

$html = $style + @"
<h2>Server First-Cleanup Summary – $hostName</h2>
<table>$rows</table>
<p>Detailed log: <code>$logPath</code></p>
"@

Set-Content -Path $htmlPath -Value $html -Encoding UTF8

Write-Host "`nLog  : $logPath"
Write-Host "HTML : $htmlPath"
Write-Host "Tip  : Consider a reboot, then snapshot or sysprep your image." -ForegroundColor Cyan 
# Execute aggressive cleanup prior to image creation
.\02-Cleanup.ps1 -Aggressive
Unattend_Golden_Image015

Script 2: Baseline Audit (Microsoft.OSConfig)

The Microsoft.OSConfig module is the official solution for managing security baselines in Windows Server 2025.

Firewall Prerequisites for Online Module Installation: Ensure outbound access over HTTPS TCP 443 is permitted to: *.powershellgallery.com, cdn.powershellgallery.com, cdn.oneget.org, go.microsoft.com, aka.ms.

# Online Installation from PSGallery
Install-Module -Name Microsoft.OSConfig -Scope AllUsers -Repository PSGallery -Force
Get-Module -ListAvailable -Name Microsoft.OSConfig
Unattend_Golden_Image019
Unattend_Golden_Image021

5. Offline Security Baseline Deployment Procedure

For offline or air-gapped environments without direct internet connectivity, deploy the OSConfig module manually:

Step 1: Download & Transfer Module

# Execute on an internet-connected computer:
Save-Module -Name Microsoft.OSConfig -Path C:\Temp

# Copy the saved module folder to the target server’s module directory:
# C:\Program Files\WindowsPowerShell\Modules\Microsoft.OSConfig\
# Verify Microsoft.OSConfig.psd1 exists in the version subfolder.
Unattend_Golden_Image025

Copy Dowloaded module in

Unattend_Golden_Image027
Unattend_Golden_Image029

Step 2: Import & Verify Module Loading

Ensure that the Microsoft.OSConfig module folder is placed correctly in the server’s PowerShell modules directory: C:\Users\Administrator\Documents\WindowsPowerShell\Modules\Microsoft.OSConfig\

# Verify Module Path
$env:PSModulePath
# Force import OSConfig module
Import-Module Microsoft.OSConfig -Force
# Confirm module loaded successfully
Get-Module Microsoft.OSConfig
Get-Command -Module Microsoft.OSConfig
Unattend_Golden_Image031

Verification: The folder must contain the manifest file with the .psd1 extension.

Unattend_Golden_Image033
# Force import OSConfig module
Import-Module Microsoft.OSConfig -Force
Unattend_Golden_Image035

Verification: Run Get-Module Microsoft.OSConfig to confirm that the module is correctly loaded into memory.

Unattend_Golden_Image037
# Confirm module loaded successfully
Get-Module Microsoft.OSConfig
Get-Command -Module Microsoft.OSConfig
Unattend_Golden_Image039

Step 3: Apply Baseline & Audit Compliance

# Apply recommended Windows Server 2025 Workgroup Security Baseline
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember -Default
# View detailed compliance status across all applied rules
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember
# Count total configured settings (approx. 319 rules)
(Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember | Measure-Object).Count
# Generate a formatted list of rule compliance statuses and reasons
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember |
  ft Name, @{Name="Status";Expression={$_.Compliance.Status}}, @{Name="Reason";Expression={$_.Compliance.Reason}} -AutoSize -Wrap
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember -Default
Unattend_Golden_Image041
# View detailed compliance status across all applied rules
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember
Unattend_Golden_Image043
(Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember | Measure-Object).Count
Unattend_Golden_Image045
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember |
ft Name, @{Name="Status";Expression={$_.Compliance.Status}}, @{Name="Reason";Expression={$_.Compliance.Reason}} -AutoSize -Wrap
Unattend_Golden_Image047

6. Post-Baseline Infrastructure Fine-Tuning

Depending on operational requirements, adjust individual baseline settings while preserving core security integrity:

Remote Desktop Services (RDP Drive Redirection)

RDP Quality-of-Life

# Enable RDP Local Drive Redirection (Set value to 0)
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WS2025/MemberServer `
  -Name RemoteDesktopServicesDoNotAllowDriveRedirection -Value 0
Restart-Computer
Security Note — Production & Critical Servers:
Enabling drive redirection marks the server as ‘Non-Compliant’ against Microsoft baselines. In production environments, keep drive redirection disabled to mitigate malware transmission and data exfiltration risks.

Advanced Auditing Settings

# Enable Success and Failure logging for Detailed File Share Access
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WS2025/MemberServer `
  -Name AuditDetailedFileShare -Value 3
Restart-Computer

Security & Compliance Impact: Provides forensic visibility (Event IDs 5140, 5145) to meet compliance requirements (e.g. NIST, ISO 27001). Increase max Security log size on high-throughput file servers to avoid log wrapping.

Defender Antivirus & AppControl (Veeam Infrastructure)

# Apply Defender Antivirus & AppControl Audit Mode Baselines
Set-OSConfigDesiredConfiguration -Scenario Defender/Antivirus -Default
Set-OSConfigDesiredConfiguration -Scenario AppControl/WS2025/DefaultPolicy/Audit -Default
# Verify Effective AppControl Policies
(Get-OSConfigDesiredConfiguration -Scenario AppControl).Value.PolicyInfo | Where-Object {$_.IsEffective -eq $true }
Get-OSConfigDesiredConfiguration -Scenario AppControl
Unattend_Golden_Image049

Set-OSConfigDesiredConfiguration -Scenario AppControl/WS2025/DefaultPolicy/Audit -Default
Unattend_Golden_Image051
Veeam Infrastructure Considerations:
• Microsoft Defender:
Real-time scanning can significantly degrade backup performance. Configure mandatory exclusions for repository paths, proxy cache folders, and Veeam executables per official Veeam Best Practices.
KB1999: Antivirus Exclusions for Veeam Backup & Replication
• AppControl (Audit Mode): Logs unapproved binary execution attempts without blocking, allowing validation of Veeam components prior to block mode enforcement.
################### Antivirus Exclusions for Veeam Backup & Replication #############
############################### VEEAM BACKUP SERVER ###############################
#Change your drive letters if need
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\VeeamFLR\"
Add-MpPreference -ExclusionPath "C:\Windows\Veeam\"
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\Backup and Replication\Backup Catalog\"
Add-MpPreference -ExclusionPath "E:\vPowerNFS\NfsDatastore"
#VBRCATALOG PATH
#This path can be found in the registry under the value named CatalogPath in the key
HKLM\SOFTWARE\Veeam\Veeam Backup Catalog\
#NFS PATH
#This path can be found in the registry under the value named RootFolder in the key
HKLM\SOFTWARE\Wow6432Node\Veeam\Veeam NFS\
############################### THIS IS THE DEFAULT LOG DIRECTORY LOCATION, IF THE LOG DIRECTORY PATH HAS BEEN CHANGED, THE AV EXCLUSION MUST BE ADJUSTED TO MATCH.###############################
Add-MpPreference -ExclusionPath "C:\Windows\Temp\*\veeamflr-*.flat"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackup\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackupTemp\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\veeamdumprecorder\"
#This is a per-user folder.
Add-MpPreference -ExclusionPath "%localappdata%\Veeam\Backup\"
############################### VEEAM BACKUP ENTERPRISE MANAGER ###############################
#VBRCatalog Path
#This path can be found in the registry under the value named CatalogPath in the key HKLM\SOFTWARE\Veeam\Veeam Backup Catalog\
#This is the default log directory location; if the log directory path has been changed, the AV exclusion must be adjusted to match.
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\"
Add-MpPreference -ExclusionPath "C:\VBRCatalog"
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
############################### VMWARE BACKUP PROXY, WAN ACCELERATOR, WINDOWS REPOSITORY ###############################
#General Folders
#All Windows-based components use the following folders:
Add-MpPreference -ExclusionPath "C:\ProgramData\Veeam\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\Veeam\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\VeeamBackupTemp\"
#Instan recovery path
Add-MpPreference -ExclusionPath E:\ProgramData\Veeam\Backup\IRCache\
########################## PACKAGE SPECIFIC AV EXCLUSIONS ###############################
Veeam Installer Service Add-MpPreference -ExclusionPath "C:\Windows\Veeam\Backup\"
Veeam Backup Transport Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\Backup Transport\"
Veeam Backup vPowerNFS Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\vPowerNFS\"
Veeam CDP Proxy Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\CDP Proxy Service\"
Veeam CDP Proxy Cache Folder Add-MpPreference -ExclusionPath "C:\VeeamCDP\"
############################### HYPERV ###############################
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\vPowerNFS\"
#Instant recover write cache folder
#Review each repository's Mount Server setting and add an AV exclusion for the write cache path on the Mount Server specified.
Add-MpPreference -ExclusionPath E:\ProgramData\Veeam\Backup\IRCache\
## Hyper-V Integration
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\Hyper-V Integration\"
############################### VEEAM MOUNT SERVICE ###############################
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\Backup and Replication\"
Add-MpPreference -ExclusionPath "C:\VeeamFLR\"
Add-MpPreference -ExclusionPath "C:\Windows\Temp\*\veeamflr-*.flat"
#WAN
Add-MpPreference -ExclusionPath "C:\Program Files\Veeam\WAN Accelerator Service\"
# Tape
Add-MpPreference -ExclusionPath "C:\Program Files (x86)\Veeam\Backup Tape\"
GATEWAY
Add-MpPreference -ExclusionPath " C:\Program Files (x86)\Veeam\Backup Gate\"
Add-MpPreference -ExclusionPath "C:\Program Files\Common Files\Veeam\Backup and Replication\Log Backup Service\"
############################### REPOSITORY FILE EXTENSIONS ###############################
Add-MpPreference -ExclusionExtension
Add-MpPreference -ExclusionExtension *.erm
Add-MpPreference -ExclusionExtension *.flat
Add-MpPreference -ExclusionExtension *.vab
Add-MpPreference -ExclusionExtension *.vbk
Add-MpPreference -ExclusionExtension *.vbk.tmp
Add-MpPreference -ExclusionExtension *.vblob
Add-MpPreference -ExclusionExtension *.vbm
Add-MpPreference -ExclusionExtension *.vbm_*tmp
Add-MpPreference -ExclusionExtension *.vbm.temp
Add-MpPreference -ExclusionExtension *.vcache
Add-MpPreference -ExclusionExtension *.vib
Add-MpPreference -ExclusionExtension *.vindex
Add-MpPreference -ExclusionExtension *.vlb
Add-MpPreference -ExclusionExtension *.vmdk
Add-MpPreference -ExclusionExtension *.vrb
Add-MpPreference -ExclusionExtension *.vsb
Add-MpPreference -ExclusionExtension *.vslice
Add-MpPreference -ExclusionExtension *.vsource
Add-MpPreference -ExclusionExtension *.vsourcecopy
Add-MpPreference -ExclusionExtension *.vsourcetemp
Add-MpPreference -ExclusionExtension *.vstore
Add-MpPreference -ExclusionExtension *.vstorecopy
Add-MpPreference -ExclusionExtension *.vstoretemp

AppControl in Audit Mode (Scenario AppControl/…/Audit)

  • Impact: Minimal. Being in Audit mode, the system only logs executions that do not comply with the policy without blocking anything. The performance impact is negligible, but it serves to verify that Veeam’s proprietary drivers or binaries are not flagged as anomalous prior to enforcing a full block policy.

7. Drift Control & Unattended Sysprep Packaging

Configuring Drift Control

Important Configuration Requirement:
Apply this setting only at the end of the deployment and server configuration process.
# Enable OSConfig Drift Control (Enforce baseline every 45 minutes)
Set-OSConfigDriftControl 45

Final Sysprep & Unattend Execution

Automate Windows Server 2025 Installation with Unattend.xml

Follow these final steps to seal and generalize the Golden Image template:
1. Remove unnecessary temporary files, software, and domain associations.
2. Perform a final Windows Update cycle and reboot.
3. Shutdown the virtual machine and take a VMware Snapshot (Pre-Sysprep).
4. Confirm SetupComplete.bat exists in C:\Windows\Setup\Scripts\ to clean unattend files post-OOBE:

@echo off del /Q /F C:\Windows\System32\Sysprep\unattend.xml
del /Q /F C:\Windows\Panther\unattend.xml
del /Q /F C:\Windows\Panther\unattend\unattend.xml
del /Q /F C:\Unattend.xml exit /b 0

5. Execute the final generalization command in PowerShell / CMD:

.\sysprep.exe /generalize /oobe /shutdown /unattend:c:\windows\system32\sysprep\unattend.xml
Unattend_Golden_Image059
Unattend_Golden_Image061

Convert the VMware VM into a template and deploy a new VM from the template to verify that the golden image is functioning as expected.

Thank you for reading. I hope this guide helps you successfully implement your solution. If you have any questions or suggestions, feel free to leave a comment below.

Thank you for reading this post. Kindly share it with others.

  • Share on X (Opens in new window) X
  • Share on Reddit (Opens in new window) Reddit
  • Share on LinkedIn (Opens in new window) LinkedIn
  • Share on Facebook (Opens in new window) Facebook
  • Share on Pinterest (Opens in new window) Pinterest
  • Share on Tumblr (Opens in new window) Tumblr
  • Share on Telegram (Opens in new window) Telegram
  • Share on WhatsApp (Opens in new window) WhatsApp
  • Share on Mastodon (Opens in new window) Mastodon
  • Share on Bluesky (Opens in new window) Bluesky
Network | Monitoring Tags:dailyprompt, dailyprompt-2925

Post navigation

Previous Post: Your DR Network Is Probably the Part You Have not Tested

Related Posts

  • azure resource groups 1
    Setup Public Load Balancer in Azure AWS/Azure/OpenShift
  • How to download install and use Kitty SSH Client on Windows
    Video on how to Download and Use KiTTY SSH Client on Windows Network | Monitoring
  • maxresdefault 2 3
    How to configure WatchGuard WebCenter Network | Monitoring
  • elastic ip association error screen
    Fix Elastic IP Address Could not be Associated AWS/Azure/OpenShift
  • Emulate Cisco Router with GNS3
    How to make a router function as a switch in GNS3 Network | Monitoring
  • Fix Windows Security Blank Screen Issue
    How to Fix Windows Security Blank Screen Issue Network | Monitoring

More Related Articles

Windows Admin Center v2 2401 install 2306 to 2311 upgrade Upgrade Windows Admin Center 2306 – 2311: Install WACmg 2410 Network | Monitoring
Featured image Domain Naming System: Enabling DNS over TLS in Windows 11 Network | Monitoring
Veeam App for Splunk setup Install Splunk and Veeam App on Windows Server to monitor VBR Backup
requestedlogon Error 1385: The user has not been granted the requested logon type at this time Network | Monitoring
232323232 Your DR Network Is Probably the Part You Have not Tested Network | Monitoring
descargar jitsi meet 2 700x336 1 How to setup and configure Jitsi as SoftPhone Network | Monitoring

Leave a Reply Cancel reply

You must be logged in to post a comment.

Microsoft MVP

vexpert-badge-stars-5

Virtual Background

VEEAMLEGEND

Categories

Top 30 Sysadmin blog

veeaam100

Veeam Vanguard

  • Featured image new
    How to find out who restarted Windows Server Windows
  • Create Alarm
    How to create Alarm rules in VMware vSphere Virtualization
  • VHDX resizing and veeam back
    Hyper V Disk allocation: Why Veeam reports full size after Shrinking Windows Server
  • images
    AWS Network Adapter: Redhat to Citrix PV and AWS PV Driver AWS/Azure/OpenShift
  • ouath2final 1 e1786835965613
    How to implement Interactive Authentication using MSAL dotNET AWS/Azure/OpenShift
  • Permission1
    How to create a Shortcut That enables Standard Users to run Applications as Administrator Windows
  • How to Change Active Directory Group Scope
    How to change Active Directory Group Scope Windows Server
  • sysprep
    SysPrep was unable to validate your Windows Installation Virtualization

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 1,745 other subscribers
  • RSS - Posts
  • RSS - Comments
  • About
  • Authors
  • Write for us
  • Contact
  • Advertise with us
  • General Terms and Conditions
  • Privacy policy
  • Feedly
  • Telegram
  • Youtube
  • Facebook
  • Instagram
  • LinkedIn
  • Tumblr
  • Pinterest
  • Twitter
  • mastodon
  • Bsky

Tags

Active Directory Azure Bitlocker Microsoft Windows PowerShell WDS Windows 10 Windows 11 Windows Deployment Services Windows Server 2016

Copyright © 2026 TechDirectArchive

Loading Comments...

You must be logged in to post a comment.