test(lab): add the kit that deploys and runs the operating-system matrix
Tests\Lab\Acceptance gets the scripts of Decision 24, which extends the
acceptance of the live tests from one lab to several operating-system builds:
- Deploy-OsMatrixLab.ps1, Add-OsMatrixMachine.ps1, Complete-OsMatrixLab.ps1
and Repair-OsMatrixBoot.ps1 build NtfsSecurityOsMatrixLab with AutomatedLab
(Server 2019, 2022 and 2025 file servers, Windows 11 clients) and repair a
base image whose host-side bcdboot left an empty EFI system partition.
- Test-MatrixReadiness.ps1 and Test-MatrixCleanup.ps1 gate each run and prove
that it left nothing behind (fixture accounts, shares, folders, group
members, orphaned SIDs, profiles); -Mode Repair removes what is left.
- Run-MatrixSequence.ps1 runs the live controller for every cell of the matrix
and stops after an infrastructure failure.
- Run-MatrixLocalSuite.ps1 and Invoke-LocalSuite.ps1 run the module's own
Pester suite on each machine in both editions, elevated and as a basic user,
as scheduled tasks: a process started from a remoting session gets every
privilege enabled, which eight of the tests do not expect.
- Export-MatrixResults.ps1 collates the cells, the skipped tests and the
package hashes into the results table.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
[ CmdletBinding ( ) ]
param (
[ Parameter ( Mandatory ) ] [string] $LogPath ,
[string] $LabName = 'NtfsSecurityOsMatrixLab' ,
[string] $DomainName = 'osmatrix.net' ,
[string] $VmPath = 'V:\AutomatedLab-VMs' ,
[string] $AddressSpace = '192.168.12.0/24' ,
[string] $PesterModulePath = 'V:\Git\WindowsAccessControl\output\RequiredModules\Pester\5.7.1' ,
[string] $PowerShell7Msi = 'V:\LabSources\SoftwarePackages\PowerShell-7.6.3-win-x64.msi'
)
# Deploys an isolated AutomatedLab lab for the NTFSSecurity operating-system matrix (Decision 24): one domain controller, three file
# servers (Server 2019, 2022, 2025), and a Windows 11 client, in a domain and on a switch of their own. It touches none of the
# existing labs, machines, switches, or domains, never calls Remove-Lab, and refuses to run when the lab or a machine name exists.
# The installation password is generated here, kept in memory, and stored only where AutomatedLab stores it for every lab.
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
$stamp = '[{0:yyyy-MM-dd HH:mm:ss}Z]'
function Write-Step { param ( [string] $Message ) ( $stamp -f [DateTime] :: UtcNow ) + ' ' + $Message | Add-Content -LiteralPath $LogPath }
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity] :: GetCurrent ( )
if ( -not $principal . IsInRole ( [Security.Principal.WindowsBuiltInRole] :: Administrator ) ) { throw 'This script must run in an elevated PowerShell session.' }
( $stamp -f [DateTime] :: UtcNow ) + " START deploy-os-matrix-lab lab= $ LabName " | Set-Content -LiteralPath $LogPath
try {
Import-Module -Name AutomatedLab -ErrorAction Stop
foreach ( $path in $PesterModulePath , $PowerShell7Msi ) { if ( -not ( Test-Path -LiteralPath $path ) ) { throw " Missing payload: $ path " } }
$machines = @ (
@ { Name = 'OSDC1' ; Os = 'Windows Server 2025 Datacenter (Desktop Experience)' ; Roles = @ ( 'RootDC' ) ; Memory = 4GB ; Address = '192.168.12.10' }
@ { Name = 'OSFile25' ; Os = 'Windows Server 2025 Datacenter (Desktop Experience)' ; Roles = @ ( 'FileServer' ) ; Memory = 3GB ; Address = '192.168.12.25' }
@ { Name = 'OSFile22' ; Os = 'Windows Server 2022 Datacenter (Desktop Experience)' ; Roles = @ ( 'FileServer' ) ; Memory = 3GB ; Address = '192.168.12.22' }
@ { Name = 'OSFile19' ; Os = 'Windows Server 2019 Datacenter (Desktop Experience)' ; Roles = @ ( 'FileServer' ) ; Memory = 3GB ; Address = '192.168.12.19' }
@ { Name = 'OSWin11' ; Os = 'Windows 11 Pro' ; Roles = @ ( ) ; Memory = 4GB ; Address = '192.168.12.11' }
)
# Collision checks from AutomatedLab metadata and from Hyper-V; the existing labs are only read.
$existingNames = New-Object System . Collections . Generic . List [string]
$labs = @ ( Get-Lab -List )
if ( $labs -contains $LabName ) { throw " The lab ' $ LabName' exists already. Refusing to redefine it. " }
foreach ( $existing in $labs ) {
Import-Lab -Name $existing -NoValidation -NoDisplay -ErrorAction Stop
foreach ( $vm in Get-LabVM -IncludeLinux ) { $existingNames . Add ( $vm . Name ) }
}
foreach ( $vm in Get-VM ) { $existingNames . Add ( $vm . Name ) }
$collisions = @ ( $machines . Name | Where-Object { $_ -in $existingNames } )
if ( $collisions ) { throw " Machine name collision: $( $collisions -join ', ' ) " }
if ( Get-VMSwitch -Name $LabName -ErrorAction SilentlyContinue ) { throw " A virtual switch named ' $ LabName' exists already. " }
$usedAddresses = @ ( Get-NetIPAddress -AddressFamily IPv4 | ForEach-Object { $_ . IPAddress } )
if ( $usedAddresses | Where-Object { $_ -like '192.168.12.*' } ) { throw 'The address space 192.168.12.0/24 is in use on the host.' }
Write-Step ( 'preflight ok; existing labs: {0}; existing machine names: {1}' -f ( $labs -join ', ' ) , $existingNames . Count )
$characters = ( [char[]] ( 48 . . 57 ) + [char[]] ( 65 . . 90 ) + [char[]] ( 97 . . 122 ) + '!' , '#' , '%' , '+' , '-' , '=' )
# A cryptographic generator, without the bias of a remainder: this is the installation and domain administrator password of the lab.
$generator = [Security.Cryptography.RandomNumberGenerator] :: Create ( )
$limit = 256 - ( 256 % $characters . Count )
$buffer = New-Object -TypeName 'byte[]' -ArgumentList 1
$chosen = New-Object -TypeName 'System.Text.StringBuilder'
while ( $chosen . Length -lt 24 ) {
$generator . GetBytes ( $buffer )
if ( $buffer [ 0 ] -lt $limit ) { $null = $chosen . Append ( $characters [ $buffer [ 0 ] % $characters . Count ] ) }
}
$password = 'Aa1!' + $chosen . ToString ( )
test(lab): add the kit that deploys and runs the operating-system matrix
Tests\Lab\Acceptance gets the scripts of Decision 24, which extends the
acceptance of the live tests from one lab to several operating-system builds:
- Deploy-OsMatrixLab.ps1, Add-OsMatrixMachine.ps1, Complete-OsMatrixLab.ps1
and Repair-OsMatrixBoot.ps1 build NtfsSecurityOsMatrixLab with AutomatedLab
(Server 2019, 2022 and 2025 file servers, Windows 11 clients) and repair a
base image whose host-side bcdboot left an empty EFI system partition.
- Test-MatrixReadiness.ps1 and Test-MatrixCleanup.ps1 gate each run and prove
that it left nothing behind (fixture accounts, shares, folders, group
members, orphaned SIDs, profiles); -Mode Repair removes what is left.
- Run-MatrixSequence.ps1 runs the live controller for every cell of the matrix
and stops after an infrastructure failure.
- Run-MatrixLocalSuite.ps1 and Invoke-LocalSuite.ps1 run the module's own
Pester suite on each machine in both editions, elevated and as a basic user,
as scheduled tasks: a process started from a remoting session gets every
privilege enabled, which eight of the tests do not expect.
- Export-MatrixResults.ps1 collates the cells, the skipped tests and the
package hashes into the results table.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
3 weeks ago
New-LabDefinition -Name $LabName -DefaultVirtualizationEngine HyperV -VmPath $VmPath
Add-LabVirtualNetworkDefinition -Name $LabName -AddressSpace $AddressSpace
Add-LabDomainDefinition -Name $DomainName -AdminUser 'install' -AdminPassword $password
Set-LabInstallationCredential -Username 'install' -Password $password
foreach ( $definition in $machines ) {
$parameters = @ {
Name = $definition . Name ; DomainName = $DomainName ; OperatingSystem = $definition . Os ; Memory = $definition . Memory
Processors = 2 ; Network = $LabName ; IpAddress = $definition . Address
}
if ( $definition . Roles . Count -gt 0 ) { $parameters . Roles = $definition . Roles }
if ( $definition . Os -like 'Windows 11*' ) {
$parameters . HypervProperties = @ { EnableSecureBoot = 'On' ; SecureBootTemplate = 'MicrosoftWindows' ; EnableTpm = 'true' }
}
Add-LabMachineDefinition @parameters
}
Write-Step 'lab defined; installing network switches and base images'
Install-Lab -NetworkSwitches -BaseImages
Write-Step 'network switches and base images done'
Install-Lab
Write-Step 'machines, domain, and roles done'
$labMachines = Get-LabVM
Install-LabSoftwarePackage -ComputerName $labMachines -Path $PowerShell7Msi -CommandLine '/quiet /norestart ADD_PATH=1' -Timeout 30
Write-Step 'PowerShell 7 installed'
foreach ( $modulesRoot in 'C:\Program Files\WindowsPowerShell\Modules' , 'C:\Program Files\PowerShell\Modules' ) {
$destination = Join-Path $modulesRoot 'Pester'
Invoke-LabCommand -ComputerName $labMachines -ActivityName 'Create the Pester module directory' -ScriptBlock { param ( $Path ) $null = New-Item -Path $Path -ItemType Directory -Force } -ArgumentList $destination -NoDisplay
Copy-LabFileItem -Path $PesterModulePath -ComputerName $labMachines -DestinationFolderPath $destination -Recurse
}
Write-Step 'Pester 5.7.1 copied'
Show-LabDeploymentSummary -Summary
Write-Step " deploy-os-matrix-lab-DONE "
exit 0
}
catch {
Write-Step ( " deploy-os-matrix-lab-FAILED: {0} " -f $_ )
$_ | Format-List -Property * -Force | Out-String | Add-Content -LiteralPath $LogPath
exit 1
}