Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74235ec6f8 | ||
|
|
0b510082d4 | ||
|
|
5e62a65316 |
@@ -111,7 +111,13 @@ ports remain private even though the VM owns a public IP.
|
|||||||
Never disable the built-in Microsoft password Credential Provider. It is the
|
Never disable the built-in Microsoft password Credential Provider. It is the
|
||||||
supported recovery path if a third-party provider fails to load.
|
supported recovery path if a third-party provider fails to load.
|
||||||
|
|
||||||
For a clean machine, the supported entry points are the release packages:
|
For a clean machine, choose the release package that matches the workstation:
|
||||||
|
|
||||||
|
- `sgu-windows10-legacy-client-bootstrap-VERSION.zip` for Windows 10;
|
||||||
|
- `sgu-windows11-client-bootstrap-VERSION.zip` for Windows 11, including the
|
||||||
|
modern Azure P2S/pre-logon flow.
|
||||||
|
|
||||||
|
Both use the same direct-lab entry point:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
Start-SguServerBootstrap.cmd 192.168.50.10
|
Start-SguServerBootstrap.cmd 192.168.50.10
|
||||||
@@ -123,6 +129,10 @@ omite y ese adaptador todavía usa una dirección `169.254.x.x`, el enrolador la
|
|||||||
solicita de forma interactiva. En equipos con dos NIC selecciona el adaptador
|
solicita de forma interactiva. En equipos con dos NIC selecciona el adaptador
|
||||||
sin puerta de enlace y conserva el `Default Switch` para Internet.
|
sin puerta de enlace y conserva el `Default Switch` para Internet.
|
||||||
|
|
||||||
|
El manifiesto identifica el perfil `Windows10Legacy` o `Windows11Modern` y el
|
||||||
|
bootstrap valida el build antes de hacer cambios. Las correcciones comunes se
|
||||||
|
mantienen en ambos; Windows 11 conserva además sus puntos de entrada modernos.
|
||||||
|
|
||||||
Linux clients are enrolled through their native PAM/SSSD stack instead of the
|
Linux clients are enrolled through their native PAM/SSSD stack instead of the
|
||||||
Windows Credential Provider:
|
Windows Credential Provider:
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ $w11 = .\scripts\New-SguAzureP2sCertificates.ps1 -ClientName 'Windows11'
|
|||||||
|
|
||||||
Copie a la VM Windows 11 de Hyper-V:
|
Copie a la VM Windows 11 de Hyper-V:
|
||||||
|
|
||||||
- `sgu-client-bootstrap-VERSION.zip` extraído;
|
- `sgu-windows11-client-bootstrap-VERSION.zip` extraído;
|
||||||
- `$vpn.PackagePath`;
|
- `$vpn.PackagePath`;
|
||||||
- `$w11.ClientCertificatePath`;
|
- `$w11.ClientCertificatePath`;
|
||||||
- `sgu-azure-p2s-root.cer`.
|
- `sgu-azure-p2s-root.cer`.
|
||||||
|
|||||||
@@ -96,7 +96,9 @@ Se admiten Pro, Enterprise y Education. Windows Home no puede unirse a Active
|
|||||||
Directory local ni actuar como host RDP; el bootstrap lo detecta antes de cambiar
|
Directory local ni actuar como host RDP; el bootstrap lo detecta antes de cambiar
|
||||||
el equipo y explica que se debe actualizar la edición.
|
el equipo y explica que se debe actualizar la edición.
|
||||||
|
|
||||||
1. Descargar y extraer `sgu-client-bootstrap-VERSION.zip`.
|
1. Descargar y extraer el paquete correspondiente:
|
||||||
|
`sgu-windows10-legacy-client-bootstrap-VERSION.zip` o
|
||||||
|
`sgu-windows11-client-bootstrap-VERSION.zip`.
|
||||||
2. Ejecutar con la IP fija actual del controlador de dominio:
|
2. Ejecutar con la IP fija actual del controlador de dominio:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
@@ -111,6 +113,12 @@ del cliente, el bootstrap la solicita cuando el adaptador sólo tiene APIPA
|
|||||||
ruta predeterminada. Si falla, la ventana elevada permanece abierta y el mismo
|
ruta predeterminada. Si falla, la ventana elevada permanece abierta y el mismo
|
||||||
error queda en `C:\ProgramData\SGU\Bootstrap\Client\latest-error.log`.
|
error queda en `C:\ProgramData\SGU\Bootstrap\Client\latest-error.log`.
|
||||||
|
|
||||||
|
Cada manifiesto fija su perfil y evita cruzar paquetes: Windows 10 utiliza
|
||||||
|
`Windows10Legacy` (build menor a 22000) y Windows 11 `Windows11Modern` (build
|
||||||
|
22000 o posterior). El ZIP moderno conserva tanto el enrolamiento directo como
|
||||||
|
Azure P2S/pre-logon; el ZIP legado contiene el flujo directo. El código común y
|
||||||
|
las garantías de seguridad son idénticos.
|
||||||
|
|
||||||
Después de UAC, se solicita interactivamente la credencial autorizada para unir
|
Después de UAC, se solicita interactivamente la credencial autorizada para unir
|
||||||
equipos. La contraseña existe sólo en memoria. El bootstrap:
|
equipos. La contraseña existe sólo en memoria. El bootstrap:
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
# Enrolamiento obligatorio de clientes SGU
|
# Enrolamiento obligatorio de clientes SGU
|
||||||
|
|
||||||
Para una instalación limpia de Windows se prefiere el único punto de entrada
|
Para una instalación limpia se selecciona primero el ZIP correspondiente:
|
||||||
empaquetado:
|
|
||||||
|
- `sgu-windows10-legacy-client-bootstrap-VERSION.zip` para Windows 10;
|
||||||
|
- `sgu-windows11-client-bootstrap-VERSION.zip` para Windows 11.
|
||||||
|
|
||||||
|
Ambos conservan el punto de entrada directo:
|
||||||
|
|
||||||
```bat
|
```bat
|
||||||
Start-SguClientEnrollment.cmd 192.168.50.10 192.168.50.11
|
Start-SguClientEnrollment.cmd 192.168.50.10 192.168.50.11
|
||||||
@@ -19,6 +23,13 @@ la única NIC activa sin puerta de enlace para no reemplazar el adaptador de
|
|||||||
Internet. Ante cualquier error conserva la ventana y escribe el diagnóstico en
|
Internet. Ante cualquier error conserva la ventana y escribe el diagnóstico en
|
||||||
`C:\ProgramData\SGU\Bootstrap\Client\latest-error.log`.
|
`C:\ProgramData\SGU\Bootstrap\Client\latest-error.log`.
|
||||||
|
|
||||||
|
El manifiesto contiene el perfil `Windows10Legacy` o `Windows11Modern` y el
|
||||||
|
bootstrap rechaza un ZIP que no corresponda al build instalado. El paquete de
|
||||||
|
Windows 11 conserva además `Start-SguAzureClientEnrollment.cmd` y el instalador
|
||||||
|
P2S de equipo; el legado de Windows 10 se limita al transporte directo del
|
||||||
|
laboratorio. Credential Provider, mTLS, cuenta `alumno`, RustDesk, monitorización
|
||||||
|
y autorreparación siguen saliendo de la misma base de código.
|
||||||
|
|
||||||
El flujo administrado instala y valida el Credential Provider **antes** de
|
El flujo administrado instala y valida el Credential Provider **antes** de
|
||||||
ejecutar `Add-Computer`. La pertenencia al dominio es el último cambio; si falta
|
ejecutar `Add-Computer`. La pertenencia al dominio es el último cambio; si falta
|
||||||
el runtime, un certificado, el registro COM, la directiva predeterminada o la
|
el runtime, un certificado, el registro COM, la directiva predeterminada o la
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ param(
|
|||||||
[string]$DomainNetbios = 'LCI',
|
[string]$DomainNetbios = 'LCI',
|
||||||
[string]$ComputerOuDn,
|
[string]$ComputerOuDn,
|
||||||
[string]$NewComputerName,
|
[string]$NewComputerName,
|
||||||
|
[ValidateSet('Auto', 'Windows10Legacy', 'Windows11Modern')]
|
||||||
|
[string]$CompatibilityProfile = 'Auto',
|
||||||
[ValidateSet('Direct', 'AzureP2S')]
|
[ValidateSet('Direct', 'AzureP2S')]
|
||||||
[string]$ConnectivityMode = 'Direct',
|
[string]$ConnectivityMode = 'Direct',
|
||||||
[string]$VpnConnectionName = 'SGU Azure P2S',
|
[string]$VpnConnectionName = 'SGU Azure P2S',
|
||||||
@@ -85,6 +87,7 @@ function Assert-PackageManifest {
|
|||||||
throw "Bootstrap package integrity check failed: $($entry.Path)"
|
throw "Bootstrap package integrity check failed: $($entry.Path)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return $manifest
|
||||||
}
|
}
|
||||||
|
|
||||||
function Resolve-ClientInterfaceAlias {
|
function Resolve-ClientInterfaceAlias {
|
||||||
@@ -309,7 +312,38 @@ if (-not $ComputerOuDn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$packageRoot = $PSScriptRoot
|
$packageRoot = $PSScriptRoot
|
||||||
Assert-PackageManifest -PackageRoot $packageRoot
|
$packageManifest = Assert-PackageManifest -PackageRoot $packageRoot
|
||||||
|
$manifestProfile = if ($packageManifest.PSObject.Properties['CompatibilityProfile']) {
|
||||||
|
[string]$packageManifest.CompatibilityProfile
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
'Auto'
|
||||||
|
}
|
||||||
|
if ($CompatibilityProfile -ne 'Auto' -and $manifestProfile -ne 'Auto' -and
|
||||||
|
$CompatibilityProfile -ne $manifestProfile) {
|
||||||
|
throw "The requested compatibility profile '$CompatibilityProfile' does not match package profile '$manifestProfile'."
|
||||||
|
}
|
||||||
|
if ($CompatibilityProfile -eq 'Auto') {
|
||||||
|
$CompatibilityProfile = $manifestProfile
|
||||||
|
}
|
||||||
|
$windowsBuild = [int]$operatingSystem.BuildNumber
|
||||||
|
if ($CompatibilityProfile -eq 'Auto') {
|
||||||
|
$CompatibilityProfile = if ($windowsBuild -lt 22000) {
|
||||||
|
'Windows10Legacy'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
'Windows11Modern'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($CompatibilityProfile -eq 'Windows10Legacy' -and $windowsBuild -ge 22000) {
|
||||||
|
throw "The Windows 10 legacy package cannot enroll Windows build $windowsBuild. Use the Windows 11 modern client package."
|
||||||
|
}
|
||||||
|
if ($CompatibilityProfile -eq 'Windows11Modern' -and $windowsBuild -lt 22000) {
|
||||||
|
throw "The Windows 11 modern package cannot enroll Windows build $windowsBuild. Use the Windows 10 legacy client package."
|
||||||
|
}
|
||||||
|
if ($CompatibilityProfile -eq 'Windows10Legacy' -and $ConnectivityMode -eq 'AzureP2S') {
|
||||||
|
throw 'Azure P2S pre-logon enrollment belongs to the Windows 11 modern package. Use Direct connectivity for the Windows 10 legacy package.'
|
||||||
|
}
|
||||||
$scriptsRoot = Join-Path $packageRoot 'payload\scripts'
|
$scriptsRoot = Join-Path $packageRoot 'payload\scripts'
|
||||||
$providerPublishPath = Join-Path $packageRoot 'payload\credential-provider'
|
$providerPublishPath = Join-Path $packageRoot 'payload\credential-provider'
|
||||||
$runtimeInstaller = Get-ChildItem (Join-Path $packageRoot 'payload\prerequisites') `
|
$runtimeInstaller = Get-ChildItem (Join-Path $packageRoot 'payload\prerequisites') `
|
||||||
@@ -593,7 +627,8 @@ finally {
|
|||||||
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $priorTrustedHosts -Force
|
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $priorTrustedHosts -Force
|
||||||
}
|
}
|
||||||
if (-not $winRmWasRunning) {
|
if (-not $winRmWasRunning) {
|
||||||
Stop-Service WinRM -Force -ErrorAction SilentlyContinue
|
Stop-Service WinRM -Force -NoWait -WarningAction SilentlyContinue `
|
||||||
|
-ErrorAction SilentlyContinue
|
||||||
}
|
}
|
||||||
Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue
|
Remove-Item -LiteralPath $temporaryRoot -Recurse -Force -ErrorAction SilentlyContinue
|
||||||
$DomainCredential = $null
|
$DomainCredential = $null
|
||||||
@@ -608,6 +643,7 @@ if ($SkipRestart) {
|
|||||||
ClientCertificateRegistered = $true
|
ClientCertificateRegistered = $true
|
||||||
BrokerEndpoint = $brokerEndpoint
|
BrokerEndpoint = $brokerEndpoint
|
||||||
ConnectivityMode = $ConnectivityMode
|
ConnectivityMode = $ConnectivityMode
|
||||||
|
CompatibilityProfile = $CompatibilityProfile
|
||||||
VpnConnectionName = if ($ConnectivityMode -eq 'AzureP2S') { $VpnConnectionName } else { $null }
|
VpnConnectionName = if ($ConnectivityMode -eq 'AzureP2S') { $VpnConnectionName } else { $null }
|
||||||
RestartRequired = $true
|
RestartRequired = $true
|
||||||
RustDesk = if ($result) { $result.RustDesk } else { $null }
|
RustDesk = if ($result) { $result.RustDesk } else { $null }
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ function Write-PackageManifest {
|
|||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)][string]$PackageRoot,
|
[Parameter(Mandatory)][string]$PackageRoot,
|
||||||
[Parameter(Mandatory)][string]$PackageVersion,
|
[Parameter(Mandatory)][string]$PackageVersion,
|
||||||
[Parameter(Mandatory)][string]$PackageKind
|
[Parameter(Mandatory)][string]$PackageKind,
|
||||||
|
[ValidateSet('Windows10Legacy', 'Windows11Modern')]
|
||||||
|
[string]$CompatibilityProfile,
|
||||||
|
[string]$TargetOperatingSystem
|
||||||
)
|
)
|
||||||
|
|
||||||
$resolvedPackageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path.TrimEnd('\')
|
$resolvedPackageRoot = (Resolve-Path -LiteralPath $PackageRoot).Path.TrimEnd('\')
|
||||||
@@ -48,13 +51,19 @@ function Write-PackageManifest {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
$manifest = [ordered]@{
|
$manifest = [ordered]@{
|
||||||
SchemaVersion = 1
|
SchemaVersion = 2
|
||||||
Product = 'SGU Credential Provider'
|
Product = 'SGU Credential Provider'
|
||||||
PackageKind = $PackageKind
|
PackageKind = $PackageKind
|
||||||
Version = $PackageVersion
|
Version = $PackageVersion
|
||||||
CreatedAt = (Get-Date).ToUniversalTime().ToString('o')
|
CreatedAt = (Get-Date).ToUniversalTime().ToString('o')
|
||||||
Files = $files
|
Files = $files
|
||||||
}
|
}
|
||||||
|
if ($CompatibilityProfile) {
|
||||||
|
$manifest['CompatibilityProfile'] = $CompatibilityProfile
|
||||||
|
}
|
||||||
|
if ($TargetOperatingSystem) {
|
||||||
|
$manifest['TargetOperatingSystem'] = $TargetOperatingSystem
|
||||||
|
}
|
||||||
[IO.File]::WriteAllText(
|
[IO.File]::WriteAllText(
|
||||||
(Join-Path $resolvedPackageRoot 'package-manifest.json'),
|
(Join-Path $resolvedPackageRoot 'package-manifest.json'),
|
||||||
($manifest | ConvertTo-Json -Depth 6),
|
($manifest | ConvertTo-Json -Depth 6),
|
||||||
@@ -78,21 +87,28 @@ if (-not $runtimeInstaller) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
New-Item -ItemType Directory -Path $resolvedOutputRoot -Force | Out-Null
|
New-Item -ItemType Directory -Path $resolvedOutputRoot -Force | Out-Null
|
||||||
$clientRoot = Join-Path $resolvedOutputRoot "sgu-client-bootstrap-$Version"
|
$windows11ClientRoot = Join-Path $resolvedOutputRoot "sgu-windows11-client-bootstrap-$Version"
|
||||||
|
$windows10ClientRoot = Join-Path $resolvedOutputRoot "sgu-windows10-legacy-client-bootstrap-$Version"
|
||||||
|
$clientRoot = $windows11ClientRoot
|
||||||
$serverRoot = Join-Path $resolvedOutputRoot "sgu-server-bootstrap-$Version"
|
$serverRoot = Join-Path $resolvedOutputRoot "sgu-server-bootstrap-$Version"
|
||||||
$linuxClientRoot = Join-Path $resolvedOutputRoot "sgu-linux-client-bootstrap-$Version"
|
$linuxClientRoot = Join-Path $resolvedOutputRoot "sgu-linux-client-bootstrap-$Version"
|
||||||
$azureRoot = Join-Path $resolvedOutputRoot "sgu-azure-infrastructure-$Version"
|
$azureRoot = Join-Path $resolvedOutputRoot "sgu-azure-infrastructure-$Version"
|
||||||
$clientZip = "$clientRoot.zip"
|
$windows11ClientZip = "$windows11ClientRoot.zip"
|
||||||
|
$windows10ClientZip = "$windows10ClientRoot.zip"
|
||||||
$serverZip = "$serverRoot.zip"
|
$serverZip = "$serverRoot.zip"
|
||||||
$linuxClientZip = "$linuxClientRoot.zip"
|
$linuxClientZip = "$linuxClientRoot.zip"
|
||||||
$azureZip = "$azureRoot.zip"
|
$azureZip = "$azureRoot.zip"
|
||||||
foreach ($target in @($clientRoot,$serverRoot,$linuxClientRoot,$azureRoot,$clientZip,$serverZip,$linuxClientZip,$azureZip)) {
|
foreach ($target in @(
|
||||||
|
$windows11ClientRoot,$windows10ClientRoot,$serverRoot,$linuxClientRoot,$azureRoot,
|
||||||
|
$windows11ClientZip,$windows10ClientZip,$serverZip,$linuxClientZip,$azureZip)) {
|
||||||
if (Test-Path -LiteralPath $target) {
|
if (Test-Path -LiteralPath $target) {
|
||||||
throw "Release target already exists: $target"
|
throw "Release target already exists: $target"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
New-Item -ItemType Directory -Path $clientRoot,$serverRoot,$linuxClientRoot,$azureRoot -Force | Out-Null
|
New-Item -ItemType Directory `
|
||||||
|
-Path $windows11ClientRoot,$windows10ClientRoot,$serverRoot,$linuxClientRoot,$azureRoot `
|
||||||
|
-Force | Out-Null
|
||||||
$welcomeFontNames = @(
|
$welcomeFontNames = @(
|
||||||
'IndivisaTextSans-Regular.otf',
|
'IndivisaTextSans-Regular.otf',
|
||||||
'IndivisaTextSans-Bold.otf',
|
'IndivisaTextSans-Bold.otf',
|
||||||
@@ -105,10 +121,6 @@ Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Invoke-SguClientBootstrap.ps
|
|||||||
-Destination (Join-Path $clientRoot 'Invoke-SguClientBootstrap.ps1')
|
-Destination (Join-Path $clientRoot 'Invoke-SguClientBootstrap.ps1')
|
||||||
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguClientEnrollment.cmd') `
|
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguClientEnrollment.cmd') `
|
||||||
-Destination (Join-Path $clientRoot 'Start-SguClientEnrollment.cmd')
|
-Destination (Join-Path $clientRoot 'Start-SguClientEnrollment.cmd')
|
||||||
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguAzureClientEnrollment.cmd') `
|
|
||||||
-Destination (Join-Path $clientRoot 'Start-SguAzureClientEnrollment.cmd')
|
|
||||||
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Install-SguAzureP2sClient.ps1') `
|
|
||||||
-Destination (Join-Path $clientRoot 'Install-SguAzureP2sClient.ps1')
|
|
||||||
$clientScripts = @(
|
$clientScripts = @(
|
||||||
'Enable-LabRemoteAccess.ps1',
|
'Enable-LabRemoteAccess.ps1',
|
||||||
'Enable-SguClientMonitoring.ps1',
|
'Enable-SguClientMonitoring.ps1',
|
||||||
@@ -141,8 +153,26 @@ foreach ($fontName in $welcomeFontNames) {
|
|||||||
}
|
}
|
||||||
Copy-RequiredFile -Source $runtimeInstaller.FullName `
|
Copy-RequiredFile -Source $runtimeInstaller.FullName `
|
||||||
-Destination (Join-Path $clientRoot "payload\prerequisites\$($runtimeInstaller.Name)")
|
-Destination (Join-Path $clientRoot "payload\prerequisites\$($runtimeInstaller.Name)")
|
||||||
Write-PackageManifest -PackageRoot $clientRoot -PackageVersion $Version -PackageKind Client
|
|
||||||
Compress-Archive -Path (Join-Path $clientRoot '*') -DestinationPath $clientZip `
|
# Both Windows packages share the provider and enrollment implementation. The
|
||||||
|
# Windows 10 artifact freezes the direct-network compatibility surface, while
|
||||||
|
# the Windows 11 artifact adds the modern Azure P2S/pre-logon entry point.
|
||||||
|
Copy-Item -Path (Join-Path $windows11ClientRoot '*') `
|
||||||
|
-Destination $windows10ClientRoot -Recurse -Force
|
||||||
|
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Start-SguAzureClientEnrollment.cmd') `
|
||||||
|
-Destination (Join-Path $windows11ClientRoot 'Start-SguAzureClientEnrollment.cmd')
|
||||||
|
Copy-RequiredFile -Source (Join-Path $PSScriptRoot 'Install-SguAzureP2sClient.ps1') `
|
||||||
|
-Destination (Join-Path $windows11ClientRoot 'Install-SguAzureP2sClient.ps1')
|
||||||
|
|
||||||
|
Write-PackageManifest -PackageRoot $windows10ClientRoot -PackageVersion $Version `
|
||||||
|
-PackageKind WindowsClient -CompatibilityProfile Windows10Legacy `
|
||||||
|
-TargetOperatingSystem 'Windows 10 Pro, Enterprise, or Education (build below 22000)'
|
||||||
|
Write-PackageManifest -PackageRoot $windows11ClientRoot -PackageVersion $Version `
|
||||||
|
-PackageKind WindowsClient -CompatibilityProfile Windows11Modern `
|
||||||
|
-TargetOperatingSystem 'Windows 11 Pro, Enterprise, or Education (build 22000 or later)'
|
||||||
|
Compress-Archive -Path (Join-Path $windows10ClientRoot '*') -DestinationPath $windows10ClientZip `
|
||||||
|
-CompressionLevel Optimal
|
||||||
|
Compress-Archive -Path (Join-Path $windows11ClientRoot '*') -DestinationPath $windows11ClientZip `
|
||||||
-CompressionLevel Optimal
|
-CompressionLevel Optimal
|
||||||
|
|
||||||
# Linux clients use their native PAM/SSSD sign-in stack rather than the Windows
|
# Linux clients use their native PAM/SSSD sign-in stack rather than the Windows
|
||||||
@@ -242,7 +272,8 @@ Compress-Archive -Path (Join-Path $azureRoot '*') -DestinationPath $azureZip `
|
|||||||
-CompressionLevel Optimal
|
-CompressionLevel Optimal
|
||||||
|
|
||||||
$checksums = @(
|
$checksums = @(
|
||||||
("{0} {1}" -f (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash, (Split-Path $clientZip -Leaf))
|
("{0} {1}" -f (Get-FileHash -LiteralPath $windows10ClientZip -Algorithm SHA256).Hash, (Split-Path $windows10ClientZip -Leaf))
|
||||||
|
("{0} {1}" -f (Get-FileHash -LiteralPath $windows11ClientZip -Algorithm SHA256).Hash, (Split-Path $windows11ClientZip -Leaf))
|
||||||
("{0} {1}" -f (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash, (Split-Path $serverZip -Leaf))
|
("{0} {1}" -f (Get-FileHash -LiteralPath $serverZip -Algorithm SHA256).Hash, (Split-Path $serverZip -Leaf))
|
||||||
("{0} {1}" -f (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash, (Split-Path $linuxClientZip -Leaf))
|
("{0} {1}" -f (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash, (Split-Path $linuxClientZip -Leaf))
|
||||||
("{0} {1}" -f (Get-FileHash -LiteralPath $azureZip -Algorithm SHA256).Hash, (Split-Path $azureZip -Leaf))
|
("{0} {1}" -f (Get-FileHash -LiteralPath $azureZip -Algorithm SHA256).Hash, (Split-Path $azureZip -Leaf))
|
||||||
@@ -252,8 +283,10 @@ $checksumsPath = Join-Path $resolvedOutputRoot "SHA256SUMS-$Version.txt"
|
|||||||
|
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Version = $Version
|
Version = $Version
|
||||||
ClientPackage = $clientZip
|
Windows10LegacyClientPackage = $windows10ClientZip
|
||||||
ClientSha256 = (Get-FileHash -LiteralPath $clientZip -Algorithm SHA256).Hash
|
Windows10LegacyClientSha256 = (Get-FileHash -LiteralPath $windows10ClientZip -Algorithm SHA256).Hash
|
||||||
|
Windows11ClientPackage = $windows11ClientZip
|
||||||
|
Windows11ClientSha256 = (Get-FileHash -LiteralPath $windows11ClientZip -Algorithm SHA256).Hash
|
||||||
LinuxClientPackage = $linuxClientZip
|
LinuxClientPackage = $linuxClientZip
|
||||||
LinuxClientSha256 = (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash
|
LinuxClientSha256 = (Get-FileHash -LiteralPath $linuxClientZip -Algorithm SHA256).Hash
|
||||||
ServerPackage = $serverZip
|
ServerPackage = $serverZip
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ param(
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$tagName = "v$Version"
|
$tagName = "v$Version"
|
||||||
$assetPaths = @(
|
$assetPaths = @(
|
||||||
(Join-Path $ReleaseDirectory "sgu-client-bootstrap-$Version.zip"),
|
(Join-Path $ReleaseDirectory "sgu-windows10-legacy-client-bootstrap-$Version.zip"),
|
||||||
|
(Join-Path $ReleaseDirectory "sgu-windows11-client-bootstrap-$Version.zip"),
|
||||||
(Join-Path $ReleaseDirectory "sgu-server-bootstrap-$Version.zip"),
|
(Join-Path $ReleaseDirectory "sgu-server-bootstrap-$Version.zip"),
|
||||||
(Join-Path $ReleaseDirectory "sgu-linux-client-bootstrap-$Version.zip"),
|
(Join-Path $ReleaseDirectory "sgu-linux-client-bootstrap-$Version.zip"),
|
||||||
(Join-Path $ReleaseDirectory "sgu-azure-infrastructure-$Version.zip"),
|
(Join-Path $ReleaseDirectory "sgu-azure-infrastructure-$Version.zip"),
|
||||||
@@ -111,7 +112,9 @@ Bootstrap reproducible para el laboratorio SGU.
|
|||||||
|
|
||||||
- **Advertencia:** el bootstrap de servidor crea un bosque nuevo. No restaura los SID, contraseñas ni relaciones de confianza del bosque anterior; para conservarlos se requiere una recuperación de bosque desde una copia de estado del sistema.
|
- **Advertencia:** el bootstrap de servidor crea un bosque nuevo. No restaura los SID, contraseñas ni relaciones de confianza del bosque anterior; para conservarlos se requiere una recuperación de bosque desde una copia de estado del sistema.
|
||||||
- `sgu-server-bootstrap-$Version.zip`: crea el bosque AD/DNS, OUs, grupo RDP, GPO, recurso `Packages`, broker mTLS y administración remota; se reanuda solo después del reinicio.
|
- `sgu-server-bootstrap-$Version.zip`: crea el bosque AD/DNS, OUs, grupo RDP, GPO, recurso `Packages`, broker mTLS y administración remota; se reanuda solo después del reinicio.
|
||||||
- `sgu-client-bootstrap-$Version.zip`: registra un certificado mTLS único, instala y valida el Credential Provider antes de unir el equipo al dominio, habilita RDP/WinRM y se repara al arranque.
|
- `sgu-windows10-legacy-client-bootstrap-$Version.zip`: perfil directo para Windows 10 de laboratorio, con las correcciones de NIC privada, límites de cuentas locales y compatibilidad de sus APIs heredadas.
|
||||||
|
- `sgu-windows11-client-bootstrap-$Version.zip`: perfil completo para Windows 11; conserva el enrolamiento directo y añade Azure P2S con certificado de máquina y entrada previa al inicio de sesión.
|
||||||
|
- Ambos clientes comparten los mismos binarios, seguridad mTLS, Credential Provider, cuenta estándar, RustDesk, supervisión y autorreparación; el manifiesto impide ejecutar accidentalmente el paquete de la otra versión de Windows.
|
||||||
- En clientes Hyper-V con dos NIC, el bootstrap selecciona la red privada sin puerta de enlace, solicita o acepta la IP fija del cliente, espera a que la dirección y WinRM estén disponibles y conserva en pantalla y archivo cualquier error de enrolamiento.
|
- En clientes Hyper-V con dos NIC, el bootstrap selecciona la red privada sin puerta de enlace, solicita o acepta la IP fija del cliente, espera a que la dirección y WinRM estén disponibles y conserva en pantalla y archivo cualquier error de enrolamiento.
|
||||||
- `sgu-linux-client-bootstrap-$Version.zip`: une clientes Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux con realmd, Kerberos y SSSD. Solicita interactivamente la contraseña de unión y no instala el Credential Provider de Windows.
|
- `sgu-linux-client-bootstrap-$Version.zip`: une clientes Debian/Ubuntu o RHEL/Fedora/Rocky/AlmaLinux con realmd, Kerberos y SSSD. Solicita interactivamente la contraseña de unión y no instala el Credential Provider de Windows.
|
||||||
- `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente.
|
- `sgu-azure-infrastructure-$Version.zip`: despliega mediante Bicep una VM Windows Server 2025, red privada, IP pública protegida por NSG y Azure VPN Gateway P2S; también genera certificados por equipo y descarga el perfil de cliente.
|
||||||
@@ -120,6 +123,8 @@ Bootstrap reproducible para el laboratorio SGU.
|
|||||||
- El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea cada grupo dentro de la OU de su rol y migra idempotentemente cualquier grupo heredado sin cambiar su SID.
|
- El Auth Broker clasifica sin tareas programadas cada cuenta autenticada: `AL` se agrega a `SGU-Alumnos`, `AD` a `SGU-Administrativos` y `DO` a `SGU-Docentes`; el bootstrap crea cada grupo dentro de la OU de su rol y migra idempotentemente cualquier grupo heredado sin cambiar su SID.
|
||||||
- El Auth Broker resuelve la dirección guardada de administrativos y docentes mediante `GetDireccion`, `GetLocalidadListado` y `GetColoniasListado`, evitando conservar los valores transitorios `Seleccione...` de los controles dinámicos de SGU.
|
- El Auth Broker resuelve la dirección guardada de administrativos y docentes mediante `GetDireccion`, `GetLocalidadListado` y `GetColoniasListado`, evitando conservar los valores transitorios `Seleccione...` de los controles dinámicos de SGU.
|
||||||
- El enrolamiento y la reparación de clientes Windows crean y verifican idempotentemente la cuenta local estándar `alumno`, sin pertenencia al grupo de administradores.
|
- El enrolamiento y la reparación de clientes Windows crean y verifican idempotentemente la cuenta local estándar `alumno`, sin pertenencia al grupo de administradores.
|
||||||
|
- La descripción de la cuenta local administrada respeta el límite de 48 caracteres de Windows 10 Enterprise.
|
||||||
|
- La validación de expiración de contraseña usa el indicador de cuenta compatible con Windows 10 y 11, en lugar de una propiedad que Windows 10 no expone.
|
||||||
- El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral.
|
- El enriquecimiento obtiene el sexo de los módulos SGU de personal/alumnos, lo conserva como la línea administrada `SGU-Gender: Male|Female` en Notas de AD y adapta el fondo de Windows/Linux; cuando falta utiliza redacción neutral.
|
||||||
- El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker.
|
- El servidor configura WEF/WEC para registrar sesiones y fallos, inventariar el estado alcanzable de las máquinas cada cinco minutos y conservar durante 183 días tanto esos eventos como el diagnóstico estructurado del Auth Broker.
|
||||||
- Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.
|
- Windows Home se detecta y se rechaza con una explicación, ya que no admite unión a Active Directory ni RDP host.
|
||||||
|
|||||||
@@ -5,7 +5,15 @@ param()
|
|||||||
$ErrorActionPreference = 'Stop'
|
$ErrorActionPreference = 'Stop'
|
||||||
$userName = 'alumno'
|
$userName = 'alumno'
|
||||||
$plainTextPassword = 'ingenieria'
|
$plainTextPassword = 'ingenieria'
|
||||||
$description = 'Cuenta local estandar de recuperacion para equipos SGU'
|
$description = 'Cuenta local estandar SGU para recuperacion'
|
||||||
|
$passwordNeverExpiresFlag = 0x10000
|
||||||
|
|
||||||
|
function Get-LocalUserFlags {
|
||||||
|
param([Parameter(Mandatory)][string]$Name)
|
||||||
|
|
||||||
|
$directoryEntry = [ADSI]("WinNT://$env:COMPUTERNAME/$Name,user")
|
||||||
|
return [int]$directoryEntry.InvokeGet('UserFlags')
|
||||||
|
}
|
||||||
|
|
||||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||||
@@ -40,6 +48,16 @@ try {
|
|||||||
-Description $description | Out-Null
|
-Description $description | Out-Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Windows 10's Get-LocalUser object has PasswordExpires but does not expose
|
||||||
|
# PasswordNeverExpires. Enforce and verify the underlying UF_DONT_EXPIRE_PASSWD
|
||||||
|
# flag so the result is consistent across Windows 10 and Windows 11.
|
||||||
|
$directoryEntry = [ADSI]("WinNT://$env:COMPUTERNAME/$userName,user")
|
||||||
|
$userFlags = [int]$directoryEntry.InvokeGet('UserFlags')
|
||||||
|
if (($userFlags -band $passwordNeverExpiresFlag) -eq 0) {
|
||||||
|
$directoryEntry.InvokeSet('UserFlags', ($userFlags -bor $passwordNeverExpiresFlag))
|
||||||
|
$directoryEntry.CommitChanges()
|
||||||
|
}
|
||||||
|
|
||||||
$user = Get-LocalUser -Name $userName -ErrorAction Stop
|
$user = Get-LocalUser -Name $userName -ErrorAction Stop
|
||||||
$administratorsSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
$administratorsSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
||||||
$usersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-545')
|
$usersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-545')
|
||||||
@@ -74,11 +92,16 @@ if (@($verifiedAdministrators).SID.Value -contains $verifiedUser.SID.Value) {
|
|||||||
if ($verifiedUsers.SID.Value -notcontains $verifiedUser.SID.Value) {
|
if ($verifiedUsers.SID.Value -notcontains $verifiedUser.SID.Value) {
|
||||||
throw "The local account '$userName' does not belong to the local Users group."
|
throw "The local account '$userName' does not belong to the local Users group."
|
||||||
}
|
}
|
||||||
|
$verifiedPasswordNeverExpires =
|
||||||
|
((Get-LocalUserFlags -Name $userName) -band $passwordNeverExpiresFlag) -ne 0
|
||||||
|
if (-not $verifiedPasswordNeverExpires) {
|
||||||
|
throw "The local account '$userName' password is not configured to never expire."
|
||||||
|
}
|
||||||
|
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
UserName = $verifiedUser.Name
|
UserName = $verifiedUser.Name
|
||||||
Enabled = $verifiedUser.Enabled
|
Enabled = $verifiedUser.Enabled
|
||||||
IsAdministrator = $false
|
IsAdministrator = $false
|
||||||
IsStandardUser = $true
|
IsStandardUser = $true
|
||||||
PasswordNeverExpires = $verifiedUser.PasswordNeverExpires
|
PasswordNeverExpires = $verifiedPasswordNeverExpires
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,12 @@ set "SGU_BOOTSTRAP_IP=%~1"
|
|||||||
set "SGU_VPN_PACKAGE=%~2"
|
set "SGU_VPN_PACKAGE=%~2"
|
||||||
set "SGU_VPN_PFX=%~3"
|
set "SGU_VPN_PFX=%~3"
|
||||||
set "SGU_VPN_ROOT=%~4"
|
set "SGU_VPN_ROOT=%~4"
|
||||||
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$script = Join-Path '%~dp0' 'Invoke-SguClientBootstrap.ps1'; $arguments = @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',('"' + $script + '"'),'-ConnectivityMode','AzureP2S'); if ($env:SGU_BOOTSTRAP_IP) { $arguments += @('-DomainControllerIPv4Address',('"' + $env:SGU_BOOTSTRAP_IP + '"')) }; if ($env:SGU_VPN_PACKAGE) { $arguments += @('-VpnProfilePackagePath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_PACKAGE) + '"')) }; if ($env:SGU_VPN_PFX) { $arguments += @('-VpnClientCertificatePfxPath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_PFX) + '"')) }; if ($env:SGU_VPN_ROOT) { $arguments += @('-VpnClientRootCertificatePath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_ROOT) + '"')) }; $process = Start-Process -FilePath powershell.exe -Verb RunAs -ArgumentList $arguments -Wait -PassThru; exit $process.ExitCode"
|
powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -Command "$script = Join-Path '%~dp0' 'Invoke-SguClientBootstrap.ps1'; $arguments = @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',('"' + $script + '"'),'-PauseOnError','-ConnectivityMode','AzureP2S'); if ($env:SGU_BOOTSTRAP_IP) { $arguments += @('-DomainControllerIPv4Address',('"' + $env:SGU_BOOTSTRAP_IP + '"')) }; if ($env:SGU_VPN_PACKAGE) { $arguments += @('-VpnProfilePackagePath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_PACKAGE) + '"')) }; if ($env:SGU_VPN_PFX) { $arguments += @('-VpnClientCertificatePfxPath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_PFX) + '"')) }; if ($env:SGU_VPN_ROOT) { $arguments += @('-VpnClientRootCertificatePath',('"' + [IO.Path]::GetFullPath($env:SGU_VPN_ROOT) + '"')) }; $process = Start-Process -FilePath powershell.exe -Verb RunAs -ArgumentList $arguments -Wait -PassThru; exit $process.ExitCode"
|
||||||
exit /b %errorlevel%
|
set "SGU_EXIT_CODE=%errorlevel%"
|
||||||
|
if not "%SGU_EXIT_CODE%"=="0" (
|
||||||
|
echo.
|
||||||
|
echo SGU Windows 11 Azure enrollment did not complete. Review:
|
||||||
|
echo C:\ProgramData\SGU\Bootstrap\Client\latest-error.log
|
||||||
|
pause
|
||||||
|
)
|
||||||
|
exit /b %SGU_EXIT_CODE%
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ $interactiveLogonPolicyPath = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\P
|
|||||||
$settingsPath = Join-Path $env:ProgramData 'SGU\CredentialProvider\settings.json'
|
$settingsPath = Join-Path $env:ProgramData 'SGU\CredentialProvider\settings.json'
|
||||||
$issues = [Collections.Generic.List[string]]::new()
|
$issues = [Collections.Generic.List[string]]::new()
|
||||||
$standardLocalUserName = 'alumno'
|
$standardLocalUserName = 'alumno'
|
||||||
|
$passwordNeverExpiresFlag = 0x10000
|
||||||
|
|
||||||
$computer = Get-CimInstance Win32_ComputerSystem
|
$computer = Get-CimInstance Win32_ComputerSystem
|
||||||
if ($RequireDomainJoined -and -not $computer.PartOfDomain) {
|
if ($RequireDomainJoined -and -not $computer.PartOfDomain) {
|
||||||
@@ -93,8 +94,7 @@ $standardLocalUserPresent = [bool]$standardLocalUser
|
|||||||
$standardLocalUserEnabled = $standardLocalUserPresent -and $standardLocalUser.Enabled
|
$standardLocalUserEnabled = $standardLocalUserPresent -and $standardLocalUser.Enabled
|
||||||
$standardLocalUserIsAdministrator = $false
|
$standardLocalUserIsAdministrator = $false
|
||||||
$standardLocalUserInUsersGroup = $false
|
$standardLocalUserInUsersGroup = $false
|
||||||
$standardLocalUserPasswordNeverExpires =
|
$standardLocalUserPasswordNeverExpires = $false
|
||||||
$standardLocalUserPresent -and $standardLocalUser.PasswordNeverExpires
|
|
||||||
if ($standardLocalUserPresent) {
|
if ($standardLocalUserPresent) {
|
||||||
$administratorsSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
$administratorsSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-544')
|
||||||
$usersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-545')
|
$usersSid = [Security.Principal.SecurityIdentifier]::new('S-1-5-32-545')
|
||||||
@@ -106,6 +106,16 @@ if ($standardLocalUserPresent) {
|
|||||||
$administratorMembers.SID.Value -contains $standardLocalUser.SID.Value
|
$administratorMembers.SID.Value -contains $standardLocalUser.SID.Value
|
||||||
$standardLocalUserInUsersGroup =
|
$standardLocalUserInUsersGroup =
|
||||||
$standardMembers.SID.Value -contains $standardLocalUser.SID.Value
|
$standardMembers.SID.Value -contains $standardLocalUser.SID.Value
|
||||||
|
try {
|
||||||
|
$directoryEntry = [ADSI]("WinNT://$env:COMPUTERNAME/$standardLocalUserName,user")
|
||||||
|
$userFlags = [int]$directoryEntry.InvokeGet('UserFlags')
|
||||||
|
$standardLocalUserPasswordNeverExpires =
|
||||||
|
($userFlags -band $passwordNeverExpiresFlag) -ne 0
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Report the account as invalid when Windows cannot read its flags.
|
||||||
|
$standardLocalUserPasswordNeverExpires = $false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (-not $standardLocalUserPresent) {
|
if (-not $standardLocalUserPresent) {
|
||||||
$issues.Add("The required standard local user '$standardLocalUserName' is missing.")
|
$issues.Add("The required standard local user '$standardLocalUserName' is missing.")
|
||||||
@@ -120,7 +130,7 @@ elseif (-not $standardLocalUserInUsersGroup) {
|
|||||||
$issues.Add("The required standard local user '$standardLocalUserName' does not belong to the local Users group.")
|
$issues.Add("The required standard local user '$standardLocalUserName' does not belong to the local Users group.")
|
||||||
}
|
}
|
||||||
elseif (-not $standardLocalUserPasswordNeverExpires) {
|
elseif (-not $standardLocalUserPasswordNeverExpires) {
|
||||||
$issues.Add("The required standard local user '$standardLocalUserName' does not retain its enrollment password.")
|
$issues.Add("The required standard local user '$standardLocalUserName' password is not configured to never expire.")
|
||||||
}
|
}
|
||||||
|
|
||||||
$settings = $null
|
$settings = $null
|
||||||
|
|||||||
@@ -72,6 +72,8 @@ Describe 'SGU public-cloud network safety' {
|
|||||||
'ConnectivityMode') | Should Be $true
|
'ConnectivityMode') | Should Be $true
|
||||||
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
||||||
'VpnProfilePackagePath') | Should Be $true
|
'VpnProfilePackagePath') | Should Be $true
|
||||||
|
((Get-Command $clientBootstrapPath).Parameters.Keys -contains
|
||||||
|
'CompatibilityProfile') | Should Be $true
|
||||||
}
|
}
|
||||||
|
|
||||||
It 'accepts an explicit static IPv4 address for a private Windows adapter' {
|
It 'accepts an explicit static IPv4 address for a private Windows adapter' {
|
||||||
@@ -106,6 +108,16 @@ Describe 'SGU public-cloud network safety' {
|
|||||||
$source | Should Match 'Wait-TcpPort -Address \$DomainControllerIPv4Address -Port 5985'
|
$source | Should Match 'Wait-TcpPort -Address \$DomainControllerIPv4Address -Port 5985'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
It 'keeps legacy and modern Windows package profiles isolated by build' {
|
||||||
|
$source = Get-Content -LiteralPath $clientBootstrapPath -Raw
|
||||||
|
$source.Contains("if (`$CompatibilityProfile -eq 'Windows10Legacy' -and `$windowsBuild -ge 22000)") |
|
||||||
|
Should Be $true
|
||||||
|
$source.Contains("if (`$CompatibilityProfile -eq 'Windows11Modern' -and `$windowsBuild -lt 22000)") |
|
||||||
|
Should Be $true
|
||||||
|
$source.Contains("if (`$CompatibilityProfile -eq 'Windows10Legacy' -and `$ConnectivityMode -eq 'AzureP2S')") |
|
||||||
|
Should Be $true
|
||||||
|
}
|
||||||
|
|
||||||
It 'uses an all-user machine-certificate VPN profile' {
|
It 'uses an all-user machine-certificate VPN profile' {
|
||||||
$source = Get-Content -LiteralPath $azureClientPath -Raw
|
$source = Get-Content -LiteralPath $azureClientPath -Raw
|
||||||
$source | Should Match '-AuthenticationMethod MachineCertificate'
|
$source | Should Match '-AuthenticationMethod MachineCertificate'
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
$repositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||||
|
$localUserScriptPath = Join-Path $repositoryRoot 'scripts\Set-SguStandardLocalUser.ps1'
|
||||||
|
$enrollmentTestScriptPath = Join-Path $repositoryRoot 'scripts\Test-SguClientEnrollment.ps1'
|
||||||
|
$packageScriptPath = Join-Path $repositoryRoot 'scripts\New-SguBootstrapPackages.ps1'
|
||||||
|
$releaseScriptPath = Join-Path $repositoryRoot 'scripts\Publish-GiteaRelease.ps1'
|
||||||
|
$azureLauncherPath = Join-Path $repositoryRoot 'scripts\Start-SguAzureClientEnrollment.cmd'
|
||||||
|
|
||||||
|
$tokens = $null
|
||||||
|
$parseErrors = $null
|
||||||
|
$scriptAst = [Management.Automation.Language.Parser]::ParseFile(
|
||||||
|
$localUserScriptPath,
|
||||||
|
[ref]$tokens,
|
||||||
|
[ref]$parseErrors)
|
||||||
|
if ($parseErrors.Count -gt 0) {
|
||||||
|
throw ($parseErrors -join [Environment]::NewLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
$descriptionAssignment = $scriptAst.Find({
|
||||||
|
param($node)
|
||||||
|
$node -is [Management.Automation.Language.AssignmentStatementAst] -and
|
||||||
|
$node.Left.Extent.Text -eq '$description'
|
||||||
|
}, $true)
|
||||||
|
$description = $descriptionAssignment.Right.Extent.Text.Trim("'")
|
||||||
|
|
||||||
|
Describe 'SGU Windows client enrollment scripts' {
|
||||||
|
It 'keeps the local-user description within the Windows 10 limit' {
|
||||||
|
($description.Length -le 48) | Should Be $true
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'declares the managed local student account' {
|
||||||
|
$source = Get-Content -LiteralPath $localUserScriptPath -Raw
|
||||||
|
$source | Should Match "\$userName = 'alumno'"
|
||||||
|
$source | Should Match "\$plainTextPassword = 'ingenieria'"
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'uses the cross-version Windows account flag for password expiration' {
|
||||||
|
$localUserSource = Get-Content -LiteralPath $localUserScriptPath -Raw
|
||||||
|
$enrollmentTestSource = Get-Content -LiteralPath $enrollmentTestScriptPath -Raw
|
||||||
|
$localUserSource | Should Match '\$passwordNeverExpiresFlag = 0x10000'
|
||||||
|
$enrollmentTestSource | Should Match '\$passwordNeverExpiresFlag = 0x10000'
|
||||||
|
$localUserSource | Should Not Match '\$verifiedUser\.PasswordNeverExpires'
|
||||||
|
$enrollmentTestSource | Should Not Match '\$standardLocalUser\.PasswordNeverExpires'
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'publishes separate legacy Windows 10 and modern Windows 11 artifacts' {
|
||||||
|
$packageSource = Get-Content -LiteralPath $packageScriptPath -Raw
|
||||||
|
$releaseSource = Get-Content -LiteralPath $releaseScriptPath -Raw
|
||||||
|
$packageSource | Should Match 'sgu-windows10-legacy-client-bootstrap-\$Version'
|
||||||
|
$packageSource | Should Match 'sgu-windows11-client-bootstrap-\$Version'
|
||||||
|
$packageSource | Should Match '-CompatibilityProfile Windows10Legacy'
|
||||||
|
$packageSource | Should Match '-CompatibilityProfile Windows11Modern'
|
||||||
|
$releaseSource | Should Match 'sgu-windows10-legacy-client-bootstrap-\$Version\.zip'
|
||||||
|
$releaseSource | Should Match 'sgu-windows11-client-bootstrap-\$Version\.zip'
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'keeps Azure P2S in the modern Windows 11 artifact' {
|
||||||
|
$packageSource = Get-Content -LiteralPath $packageScriptPath -Raw
|
||||||
|
$azureLauncher = Get-Content -LiteralPath $azureLauncherPath -Raw
|
||||||
|
$packageSource.Contains("Join-Path `$windows11ClientRoot 'Start-SguAzureClientEnrollment.cmd'") |
|
||||||
|
Should Be $true
|
||||||
|
$packageSource.Contains("Join-Path `$windows10ClientRoot 'Start-SguAzureClientEnrollment.cmd'") |
|
||||||
|
Should Be $false
|
||||||
|
$azureLauncher | Should Match '-PauseOnError'
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user