230 lines
11 KiB
PowerShell
230 lines
11 KiB
PowerShell
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
|
$bicepPath = Join-Path $repositoryRoot 'infra\azure\main.bicep'
|
|
$deploymentPath = Join-Path $repositoryRoot 'scripts\Deploy-SguAzureInfrastructure.ps1'
|
|
$configurationPath = Join-Path $repositoryRoot 'scripts\Enable-SguAzureUserRoaming.ps1'
|
|
$rollbackPath = Join-Path $repositoryRoot 'scripts\Disable-SguAzureUserRoaming.ps1'
|
|
$installerPath = Join-Path $repositoryRoot 'scripts\Install-SguFsLogix.ps1'
|
|
$gpoDeploymentPath = Join-Path $repositoryRoot 'scripts\Publish-SguFsLogixClientDeployment.ps1'
|
|
$clientBootstrapPath = Join-Path $repositoryRoot 'scripts\Invoke-SguClientBootstrap.ps1'
|
|
$azureLauncherPath = Join-Path $repositoryRoot 'scripts\Start-SguAzureClientEnrollment.cmd'
|
|
$packagePath = Join-Path $repositoryRoot 'scripts\New-SguBootstrapPackages.ps1'
|
|
|
|
foreach ($scriptPath in @(
|
|
$deploymentPath,
|
|
$configurationPath,
|
|
$rollbackPath,
|
|
$installerPath,
|
|
$gpoDeploymentPath,
|
|
$clientBootstrapPath,
|
|
$packagePath)) {
|
|
$tokens = $null
|
|
$parseErrors = $null
|
|
[Management.Automation.Language.Parser]::ParseFile(
|
|
$scriptPath,
|
|
[ref]$tokens,
|
|
[ref]$parseErrors) | Out-Null
|
|
if ($parseErrors.Count -gt 0) {
|
|
throw "$scriptPath contains PowerShell parser errors: $($parseErrors -join '; ')"
|
|
}
|
|
}
|
|
|
|
$configurationTokens = $null
|
|
$configurationParseErrors = $null
|
|
$configurationAst = [Management.Automation.Language.Parser]::ParseFile(
|
|
$configurationPath,
|
|
[ref]$configurationTokens,
|
|
[ref]$configurationParseErrors)
|
|
$samFunction = $configurationAst.Find({
|
|
param($node)
|
|
$node -is [Management.Automation.Language.FunctionDefinitionAst] -and
|
|
$node.Name -eq 'Get-SguStorageSamAccountName'
|
|
}, $true)
|
|
Invoke-Expression $samFunction.Extent.Text
|
|
|
|
Describe 'SGU Azure user-roaming infrastructure' {
|
|
It 'keeps roaming disabled by default while retaining optional private Azure Files resources' {
|
|
$source = Get-Content -LiteralPath $bicepPath -Raw
|
|
$source | Should Match 'param deployUserRoaming bool = false'
|
|
$source | Should Match "purpose: 'SGU-user-roaming'"
|
|
$source | Should Match "publicNetworkAccess: 'Disabled'"
|
|
$source | Should Match "privateLinkServiceId: userRoamingStorageAccount.id"
|
|
$source | Should Match "'file'"
|
|
$source | Should Match "privatelink\.file\.\$\{storageEndpointSuffix\}"
|
|
$source | Should Match 'dhcpOptions:'
|
|
$source | Should Match 'dnsServers:'
|
|
$source | Should Match 'domainControllerPrivateIp'
|
|
$source | Should Match 'fsLogixProfilesShare'
|
|
$source | Should Match 'redirectedFoldersShare'
|
|
}
|
|
|
|
It 'does not repurpose the temporary bootstrap staging account' {
|
|
(Get-Content -LiteralPath $bicepPath -Raw) | Should Not Match 'sgustage|SGU-bootstrap-staging'
|
|
}
|
|
|
|
It 'requires a private P2S route when roaming is enabled' {
|
|
$source = Get-Content -LiteralPath $deploymentPath -Raw
|
|
$source | Should Match '\$DeployUserRoaming -and -not \$DeployVpnGateway'
|
|
$source | Should Match 'private Azure Files endpoint'
|
|
$source | Should Match '\$DeployUserRoaming -and -not \$AcknowledgeUserRoamingRisks'
|
|
$source | Should Match '\[bool\]\$DeployUserRoaming = \$false'
|
|
}
|
|
|
|
It 'returns every post-domain setup value from the Azure deployment' {
|
|
$source = Get-Content -LiteralPath $deploymentPath -Raw
|
|
foreach ($name in @(
|
|
'UserRoamingEnabled',
|
|
'UserRoamingStorageAccountName',
|
|
'FsLogixProfilesSharePath',
|
|
'RedirectedFoldersSharePath',
|
|
'UserRoamingSetupArguments')) {
|
|
$source | Should Match $name
|
|
}
|
|
}
|
|
}
|
|
|
|
Describe 'SGU role-specific roaming policies' {
|
|
It 'requires an explicit risk acknowledgement before enabling roaming' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match '\[switch\]\$AcknowledgeUserRoamingRisks'
|
|
$source | Should Match 'optional high-risk feature'
|
|
}
|
|
|
|
It 'supports an explicitly selected public SMB endpoint while keeping private as the default' {
|
|
(((Get-Command $configurationPath).Parameters['EndpointAccess'].Attributes |
|
|
Where-Object { $_ -is [Management.Automation.ValidateSetAttribute] }).ValidValues -join ',') |
|
|
Should Be 'Private,Public'
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match ([regex]::Escape("[string]`$EndpointAccess = 'Private'"))
|
|
$source | Should Match ([regex]::Escape("`$EndpointAccess -eq 'Private'"))
|
|
$source | Should Match 'selected \$EndpointAccess endpoint'
|
|
}
|
|
|
|
It 'uses local Cloud Cache automatically for public WAN profile storage' {
|
|
(((Get-Command $configurationPath).Parameters['StaffProfileStorageMode'].Attributes |
|
|
Where-Object { $_ -is [Management.Automation.ValidateSetAttribute] }).ValidValues -join ',') |
|
|
Should Be 'Auto,Direct,CloudCache'
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match "EndpointAccess -eq 'Public'\) \{ 'CloudCache' \}"
|
|
$source | Should Match 'CCDLocations'
|
|
$source | Should Match 'type=smb,name='
|
|
$source | Should Match "ValueName 'VHDLocations'"
|
|
$source | Should Match '\*\*del\.\$ValueName'
|
|
$source | Should Match 'HealthyProvidersRequiredForRegister'
|
|
$source | Should Match 'CcdUnregisterTimeout.*60'
|
|
$source | Should Match 'ClearCacheOnForcedUnregister'
|
|
$source | Should Match 'PreventLoginWithFailure'
|
|
$source | Should Match "'HKLM\\SOFTWARE\\FSLogix\\Apps'"
|
|
$source | Should Match "ValueName 'VHDCompactDisk'"
|
|
}
|
|
|
|
It 'uses an AD computer identity with AES-256 Kerberos' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match "DomainAccountType = 'ComputerAccount'"
|
|
$source | Should Match 'KerberosEncryptionType AES256'
|
|
$source | Should Match 'PasswordNeverExpires \$true'
|
|
$source | Should Match 'StorageFileDataSmbShareContributor'
|
|
}
|
|
|
|
It 'isolates per-user directories with CREATOR OWNER ACLs' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match "SecurityIdentifier\]::new\('S-1-3-0'\)"
|
|
$source | Should Match 'PropagationFlags\]::InheritOnly'
|
|
$source | Should Match 'SetAccessRuleProtection\(\$true, \$false\)'
|
|
$source | Should Match '\$perUserRootRights'
|
|
$source | Should Match 'CreateDirectories'
|
|
$source | Should Match 'ContributorRights \$perUserRootRights'
|
|
}
|
|
|
|
It 'redirects only the student Desktop and Documents folders' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match "ValueName 'Desktop'"
|
|
$source | Should Match "ValueName 'Personal'"
|
|
$source | Should Match '%USERNAME%\\Desktop'
|
|
$source | Should Match '%USERNAME%\\Documents'
|
|
$source | Should Match "ValueName 'DisableFRAdminPin'"
|
|
}
|
|
|
|
It 'enables FSLogix only through the AD and DO group SIDs' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match "\$fsLogixRoot = 'HKLM\\SOFTWARE\\FSLogix\\Profiles'"
|
|
$source | Should Match '\\ObjectSpecific\\\$\(\$staffGroup\.SID\.Value\)'
|
|
$source | Should Match '\$professorGroup, \$administrativeGroup'
|
|
$source | Should Match "ValueName 'Enabled' -Type DWord -Value 0"
|
|
$source | Should Match 'VHDLocations = @\{ Type = ''String''; Value = \$profilesSharePath \}'
|
|
}
|
|
|
|
It 'does not delete existing staff profiles unless explicitly requested' {
|
|
$source = Get-Content -LiteralPath $configurationPath -Raw
|
|
$source | Should Match '\[switch\]\$DeleteExistingStaffLocalProfiles'
|
|
$source | Should Match 'if \(\$DeleteExistingStaffLocalProfiles\) \{ 1 \} else \{ 0 \}'
|
|
}
|
|
|
|
It 'derives valid deterministic AD names for long storage account names' {
|
|
$name = Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx'
|
|
$name.Length | Should Be 15
|
|
$name | Should Match '^sgufs[0-9a-f]{10}$'
|
|
(Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx') | Should Be $name
|
|
(Get-SguStorageSamAccountName -StorageName 'sguroam1234567') |
|
|
Should Be 'sguroam1234567'
|
|
}
|
|
}
|
|
|
|
Describe 'SGU FSLogix image enrollment' {
|
|
It 'verifies the Microsoft signature and installs unattended' {
|
|
$source = Get-Content -LiteralPath $installerPath -Raw
|
|
$source | Should Match 'Get-AuthenticodeSignature'
|
|
$source | Should Match 'CN=Microsoft Corporation'
|
|
foreach ($argument in @('/install', '/quiet', '/norestart')) {
|
|
$source | Should Match ([regex]::Escape($argument))
|
|
}
|
|
$source | Should Match "Get-Service -Name frxsvc"
|
|
}
|
|
|
|
It 'exposes the optional installer through the unified Azure launcher' {
|
|
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
|
'FsLogixInstallerPath') | Should Be $true
|
|
$launcher = Get-Content -LiteralPath $azureLauncherPath -Raw
|
|
$launcher | Should Match 'SGU_FSLOGIX_INSTALLER=%~5'
|
|
$launcher | Should Match '-FsLogixInstallerPath'
|
|
}
|
|
|
|
It 'packages both roaming setup scripts' {
|
|
$source = Get-Content -LiteralPath $packagePath -Raw
|
|
$source | Should Match "'Install-SguFsLogix\.ps1'"
|
|
$source | Should Match "'Disable-SguAzureUserRoaming\.ps1'"
|
|
$source | Should Match "'Enable-SguAzureUserRoaming\.ps1'"
|
|
$source | Should Match "'Publish-SguFsLogixClientDeployment\.ps1'"
|
|
}
|
|
|
|
It 'publishes a signed idempotent FSLogix computer startup deployment' {
|
|
$source = Get-Content -LiteralPath $gpoDeploymentPath -Raw
|
|
$source | Should Match 'Get-AuthenticodeSignature'
|
|
$source | Should Match 'ExpectedInstallerSha256'
|
|
$source | Should Match "'Machine\\Scripts'"
|
|
$source | Should Match "'Startup'"
|
|
$source | Should Match 'scripts\.ini'
|
|
$source | Should Match '42B5FAAE-6536-11D2-AE5A-0000F87571E3'
|
|
$source | Should Match 'Install-SguFsLogix-Startup\.ps1'
|
|
}
|
|
}
|
|
|
|
Describe 'SGU local-profile rollback' {
|
|
It 'disables FSLogix containers and their services through computer policy' {
|
|
$source = Get-Content -LiteralPath $rollbackPath -Raw
|
|
$source | Should Match "'HKLM\\SOFTWARE\\FSLogix\\Profiles'"
|
|
$source | Should Match "'HKLM\\SOFTWARE\\FSLogix\\ODFC'"
|
|
$source | Should Match '-ValueName Enabled -Type DWord -Value 0'
|
|
$source | Should Match "'frxsvc','frxccds'"
|
|
$source | Should Match '-ValueName Start -Type DWord -Value 4'
|
|
$source | Should Match 'StaffProfileStorageMode[\s\S]*-Value Disabled'
|
|
}
|
|
|
|
It 'restores student shell folders to the local profile and disables deployment' {
|
|
$source = Get-Content -LiteralPath $rollbackPath -Raw
|
|
$source | Should Match '%USERPROFILE%\\Desktop'
|
|
$source | Should Match '%USERPROFILE%\\Documents'
|
|
$source | Should Match "-LinkEnabled No"
|
|
$source | Should Match 'profilePath,homeDirectory,homeDrive'
|
|
}
|
|
}
|