Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da310e4213 | ||
|
|
bda8b354b7 | ||
|
|
7986b76e35 |
@@ -36,6 +36,10 @@ contrario, el contenedor de equipos configurado en AD. Los parámetros
|
||||
explícitamente. Para otra cuenta, editar el usuario sugerido como
|
||||
`DOMINIO\usuario` o `usuario@dominio`. No se guardan contraseñas.
|
||||
|
||||
El enrolamiento también oculta las entradas de Cambio rápido de usuario mediante
|
||||
una política de equipo. Docentes, administrativos y alumnos conservan la opción
|
||||
de cerrar sesión, pero no pueden dejar una sesión abierta para cambiar a otra.
|
||||
|
||||
Si la IPv4 proporcionada es pública, el mismo flujo configura automáticamente
|
||||
la conectividad directa. Después de autenticar WinRM, el servidor crea o reutiliza
|
||||
un certificado DoH, publica DNS cifrado en TCP 443 y devuelve únicamente su
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# SGU Credential Provider 0.6.1
|
||||
|
||||
Continúa la serie publicada después de `0.6.0`.
|
||||
|
||||
## Cambios
|
||||
|
||||
- El configurador de roaming admite explícitamente Azure Files mediante endpoint
|
||||
público con `-EndpointAccess Public`; el modo privado sigue siendo el valor
|
||||
predeterminado.
|
||||
- Se corrige la generación del nombre de la identidad de almacenamiento para
|
||||
respetar el límite de 15 caracteres de una cuenta de equipo de Active
|
||||
Directory.
|
||||
- Las ACL de `profiles` y `redirected` aplican mínimo privilegio en la raíz: los
|
||||
grupos autorizados pueden crear su directorio, pero no modificar ni eliminar
|
||||
directorios ajenos; `CREATOR OWNER` conserva el control dentro del directorio
|
||||
propio.
|
||||
- Se documenta la secuencia de despliegue con firewall de Azure Storage en modo
|
||||
`Deny`, autorizando finalmente sólo la red institucional `200.13.89.0/24`.
|
||||
|
||||
## Validación del despliegue
|
||||
|
||||
- Azure Files usa autenticación AD DS con Kerberos AES-256.
|
||||
- `AF-02` y `AF-03` validaron ticket Kerberos, TCP 445 y lectura/escritura en los
|
||||
shares `profiles` y `redirected`.
|
||||
- FSLogix 26.08 (`3.26.826.17182`) quedó instalado y activo en ambos clientes.
|
||||
- La GPO de FSLogix mantiene una base deshabilitada y se habilita sólo para los
|
||||
SID de `SGU-Docentes` y `SGU-Administrativos`.
|
||||
- La GPO de Alumnos redirige únicamente Escritorio y Documentos.
|
||||
|
||||
## Descargas
|
||||
|
||||
- `sgu-windows-client-bootstrap-0.6.1.zip`
|
||||
- `sgu-server-bootstrap-0.6.1.zip`
|
||||
- `sgu-linux-client-bootstrap-0.6.1.zip`
|
||||
- `sgu-azure-infrastructure-0.6.1.zip`
|
||||
- `SHA256SUMS-0.6.1.txt`
|
||||
|
||||
Verifique los hashes SHA-256 antes de ejecutar los paquetes. No se incluyen
|
||||
credenciales, claves de almacenamiento ni artefactos temporales del despliegue.
|
||||
@@ -0,0 +1,36 @@
|
||||
# SGU Credential Provider 0.6.2
|
||||
|
||||
Continúa la serie publicada después de `0.6.1`.
|
||||
|
||||
## Cambios
|
||||
|
||||
- Se agrega despliegue automático e idempotente de FSLogix 26.08 mediante una
|
||||
GPO de inicio de equipo ligada a `OU=Laboratorio`.
|
||||
- El publicador verifica la firma Authenticode de Microsoft y el SHA-256 del
|
||||
instalador antes de copiarlo al SYSVOL.
|
||||
- Los equipos que estaban apagados reciben e instalan FSLogix en su siguiente
|
||||
arranque; no se emplean tareas programadas.
|
||||
- El script queda incluido en el paquete de servidor y la guía de roaming
|
||||
documenta su publicación y comprobación.
|
||||
|
||||
## Validación del despliegue
|
||||
|
||||
- La GPO `SGU - FSLogix client deployment` está enlazada y sus versiones de AD
|
||||
y SYSVOL coinciden.
|
||||
- El instalador publicado conserva firma válida de Microsoft y SHA-256
|
||||
`0BAF7FE8195571060F0361B351E58FE6CD422AE521A96BAB3E66D900544E8A30`.
|
||||
- `AF-02` y `AF-03` aplicaron la GPO y ejecutan `frxsvc` con FSLogix
|
||||
`3.26.826.17182`.
|
||||
- Azure Storage permanece en `defaultAction=Deny` y sólo autoriza
|
||||
`200.13.89.0/24`.
|
||||
|
||||
## Descargas
|
||||
|
||||
- `sgu-windows-client-bootstrap-0.6.2.zip`
|
||||
- `sgu-server-bootstrap-0.6.2.zip`
|
||||
- `sgu-linux-client-bootstrap-0.6.2.zip`
|
||||
- `sgu-azure-infrastructure-0.6.2.zip`
|
||||
- `SHA256SUMS-0.6.2.txt`
|
||||
|
||||
Verifique los hashes SHA-256 antes de ejecutar los paquetes. No se incluyen
|
||||
credenciales, claves de almacenamiento ni artefactos temporales del despliegue.
|
||||
+33
-1
@@ -26,6 +26,16 @@ VNet. El modo de enrolamiento público directo no puede usar estos recursos y el
|
||||
script de despliegue exige `-DeployUserRoaming $false` cuando no se despliega el
|
||||
gateway P2S.
|
||||
|
||||
Para una cuenta SMB ya existente cuyo endpoint público se limite mediante el
|
||||
firewall de Storage a las redes institucionales, el configurador admite
|
||||
`-EndpointAccess Public`. En ese modo no exige una dirección RFC1918, pero sigue
|
||||
comprobando resolución DNS y TCP 445. La creación de los shares y las reglas de
|
||||
red de la cuenta existente se realiza antes de ejecutar el configurador. El host
|
||||
que aplica la integración AD/ACL debe conservar acceso temporal a SMB durante
|
||||
esa ejecución; después se establece `defaultAction=Deny` y se deja únicamente la
|
||||
red institucional autorizada. En el despliegue LCI actual esa regla final es
|
||||
`200.13.89.0/24`.
|
||||
|
||||
Azure Files se integra con el AD DS SGU, no con cuentas o claves guardadas en
|
||||
cada cliente. El script configura AES-256 para Kerberos, una ACL de mínimo
|
||||
privilegio con `CREATOR OWNER`, administradores y los grupos del rol, y permiso
|
||||
@@ -33,6 +43,11 @@ SMB predeterminado para identidades autenticadas. La ACL NTFS mantiene aislado
|
||||
el contenido de cada usuario. La clave de la cuenta se usa en memoria sólo para
|
||||
crear las ACL iniciales y no se escribe en el dominio, en GPO ni en disco.
|
||||
|
||||
Los grupos de rol sólo pueden enumerar la raíz y crear su propio directorio; no
|
||||
reciben `Modify` en la raíz compartida. El directorio creado hereda `CREATOR
|
||||
OWNER`, de modo que un usuario no puede modificar ni eliminar el contenedor de
|
||||
otro usuario.
|
||||
|
||||
## 1. Desplegar la infraestructura
|
||||
|
||||
El soporte está activado por omisión para despliegues P2S:
|
||||
@@ -100,7 +115,7 @@ dedicada para evitar que una cuenta de equipo sin agente pierda Kerberos a los
|
||||
30 días. Si la política exige rotación, use periódicamente el procedimiento
|
||||
oficial `Update-AzStorageAccountADObjectPassword` y no cambie sólo un lado.
|
||||
|
||||
## 3. Instalar FSLogix en las imágenes Windows
|
||||
## 3. Instalar FSLogix en los clientes Windows
|
||||
|
||||
FSLogix no forma parte de Windows y su licencia debe validarse para las ediciones
|
||||
y suscripciones usadas. Descargue `FSLogixAppsSetup.exe` directamente de
|
||||
@@ -117,6 +132,22 @@ imagen ya contiene FSLogix, el instalador puede omitirse. Microsoft recomienda
|
||||
mantener la versión más reciente; FSLogix no se actualiza mediante Windows
|
||||
Update.
|
||||
|
||||
Para cubrir también equipos ya unidos al dominio —incluidos los que estaban
|
||||
apagados durante el despliegue— publique el instalador como script de inicio de
|
||||
equipo:
|
||||
|
||||
```powershell
|
||||
.\payload\scripts\Publish-SguFsLogixClientDeployment.ps1
|
||||
```
|
||||
|
||||
El script descarga el paquete oficial FSLogix 26.08, valida la firma
|
||||
Authenticode de Microsoft y el SHA-256 esperado, y crea la GPO
|
||||
`SGU - FSLogix client deployment` ligada a `OU=Laboratorio`. El ejecutable queda
|
||||
en el SYSVOL de esa GPO y se instala de forma idempotente durante el arranque;
|
||||
no usa tareas programadas ni requiere que el equipo estuviera encendido al
|
||||
publicarla. Si `frxsvc` y `frx.exe` ya existen, el script de inicio termina sin
|
||||
reinstalar.
|
||||
|
||||
## 4. Validar un piloto
|
||||
|
||||
Después de `gpupdate /force` y reiniciar un cliente:
|
||||
@@ -126,6 +157,7 @@ Resolve-DnsName "$($azure.UserRoamingStorageAccountName).file.core.windows.net"
|
||||
Test-NetConnection "$($azure.UserRoamingStorageAccountName).file.core.windows.net" -Port 445
|
||||
Get-Service frxsvc
|
||||
Get-ItemProperty 'HKLM:\SOFTWARE\FSLogix\Profiles'
|
||||
Get-ItemProperty 'HKLM:\SOFTWARE\SGU\FSLogixDeployment'
|
||||
& 'C:\Program Files\FSLogix\Apps\frx.exe' list-redirects
|
||||
```
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ param(
|
||||
[string]$AdministrativeGroupName = 'SGU-Administrativos',
|
||||
[string]$StudentGpoName = 'SGU - AL redirected folders',
|
||||
[string]$StaffGpoName = 'SGU - AD-DO FSLogix profiles',
|
||||
[ValidateSet('Private', 'Public')]
|
||||
[string]$EndpointAccess = 'Private',
|
||||
[ValidateRange(1024, 1048576)]
|
||||
[int]$FsLogixProfileSizeMiB = 30000,
|
||||
[string]$AzFilesHybridModulePath,
|
||||
@@ -76,13 +78,13 @@ function Import-SguAzFilesHybrid {
|
||||
function Get-SguStorageSamAccountName {
|
||||
param([Parameter(Mandatory)][string]$StorageName)
|
||||
|
||||
if ($StorageName.Length -le 20) {
|
||||
if ($StorageName.Length -le 15) {
|
||||
return $StorageName
|
||||
}
|
||||
$sha256 = [Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
$hash = $sha256.ComputeHash([Text.Encoding]::UTF8.GetBytes($StorageName))
|
||||
$suffix = ([BitConverter]::ToString($hash) -replace '-', '').Substring(0, 15).ToLowerInvariant()
|
||||
$suffix = ([BitConverter]::ToString($hash) -replace '-', '').Substring(0, 10).ToLowerInvariant()
|
||||
return "sgufs$suffix"
|
||||
}
|
||||
finally {
|
||||
@@ -322,15 +324,19 @@ if ($PSCmdlet.ShouldProcess($StorageAccountName, 'Grant authenticated AD identit
|
||||
-DefaultSharePermission StorageFileDataSmbShareContributor
|
||||
}
|
||||
|
||||
$privateAddresses = @(Resolve-DnsName -Name $fileEndpointHost -Type A -ErrorAction Stop |
|
||||
$endpointAddresses = @(Resolve-DnsName -Name $fileEndpointHost -Type A -ErrorAction Stop |
|
||||
Where-Object IPAddress | Select-Object -ExpandProperty IPAddress)
|
||||
if ($privateAddresses.Count -eq 0 -or @($privateAddresses | Where-Object {
|
||||
if ($endpointAddresses.Count -eq 0) {
|
||||
throw "$fileEndpointHost did not resolve to an IPv4 address."
|
||||
}
|
||||
$privateAddresses = @($endpointAddresses | Where-Object {
|
||||
$_ -match '^(10\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.)'
|
||||
}).Count -eq 0) {
|
||||
})
|
||||
if ($EndpointAccess -eq 'Private' -and $privateAddresses.Count -eq 0) {
|
||||
throw "$fileEndpointHost did not resolve to a private endpoint. Verify the privatelink.file.core.windows.net VNet link and the DC Azure DNS forwarder."
|
||||
}
|
||||
if (-not (Test-NetConnection -ComputerName $fileEndpointHost -Port 445 -InformationLevel Quiet)) {
|
||||
throw "The domain controller cannot reach $fileEndpointHost on TCP 445 through the private endpoint."
|
||||
throw "The domain controller cannot reach $fileEndpointHost on TCP 445 using the selected $EndpointAccess endpoint."
|
||||
}
|
||||
|
||||
$storageKey = @(Get-AzStorageAccountKey -ResourceGroupName $ResourceGroupName `
|
||||
@@ -344,23 +350,24 @@ $storageCredential = [PSCredential]::new(
|
||||
(ConvertTo-SecureString -String $storageKey -AsPlainText -Force))
|
||||
$profilesSharePath = "\\$fileEndpointHost\$FsLogixProfilesShareName"
|
||||
$redirectedFoldersSharePath = "\\$fileEndpointHost\$RedirectedFoldersShareName"
|
||||
try {
|
||||
if ($PSCmdlet.ShouldProcess($profilesSharePath, 'Apply isolated FSLogix root ACLs')) {
|
||||
Set-SguAzureFileRootAcl -UncPath $profilesSharePath -Credential $storageCredential `
|
||||
-DomainAdminsSid $domainAdminsSid `
|
||||
-ContributorSids @($professorGroup.SID, $administrativeGroup.SID)
|
||||
}
|
||||
if ($PSCmdlet.ShouldProcess($redirectedFoldersSharePath, 'Apply isolated student-folder root ACLs')) {
|
||||
$studentRootRights = [Security.AccessControl.FileSystemRights]::CreateDirectories -bor
|
||||
$perUserRootRights = [Security.AccessControl.FileSystemRights]::CreateDirectories -bor
|
||||
[Security.AccessControl.FileSystemRights]::ListDirectory -bor
|
||||
[Security.AccessControl.FileSystemRights]::ReadAttributes -bor
|
||||
[Security.AccessControl.FileSystemRights]::ReadExtendedAttributes -bor
|
||||
[Security.AccessControl.FileSystemRights]::ReadPermissions -bor
|
||||
[Security.AccessControl.FileSystemRights]::Traverse -bor
|
||||
[Security.AccessControl.FileSystemRights]::Synchronize
|
||||
try {
|
||||
if ($PSCmdlet.ShouldProcess($profilesSharePath, 'Apply isolated FSLogix root ACLs')) {
|
||||
Set-SguAzureFileRootAcl -UncPath $profilesSharePath -Credential $storageCredential `
|
||||
-DomainAdminsSid $domainAdminsSid `
|
||||
-ContributorSids @($professorGroup.SID, $administrativeGroup.SID) `
|
||||
-ContributorRights $perUserRootRights
|
||||
}
|
||||
if ($PSCmdlet.ShouldProcess($redirectedFoldersSharePath, 'Apply isolated student-folder root ACLs')) {
|
||||
Set-SguAzureFileRootAcl -UncPath $redirectedFoldersSharePath -Credential $storageCredential `
|
||||
-DomainAdminsSid $domainAdminsSid -ContributorSids @($studentGroup.SID) `
|
||||
-ContributorRights $studentRootRights
|
||||
-ContributorRights $perUserRootRights
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -415,6 +422,8 @@ foreach ($staffGroup in @($professorGroup, $administrativeGroup)) {
|
||||
[pscustomobject]@{
|
||||
StorageAccountName = $StorageAccountName
|
||||
FileEndpoint = $fileEndpointHost
|
||||
EndpointAccess = $EndpointAccess
|
||||
EndpointAddresses = $endpointAddresses
|
||||
PrivateEndpointAddresses = $privateAddresses
|
||||
DirectoryService = $directoryService
|
||||
KerberosEncryption = 'AES256'
|
||||
|
||||
@@ -314,6 +314,11 @@ if ($PSCmdlet.ShouldProcess($installPath, 'Install and register the SGU Credenti
|
||||
-Value 1 `
|
||||
-PropertyType DWord `
|
||||
-Force | Out-Null
|
||||
New-ItemProperty -Path $interactiveLogonPolicyPath `
|
||||
-Name HideFastUserSwitching `
|
||||
-Value 1 `
|
||||
-PropertyType DWord `
|
||||
-Force | Out-Null
|
||||
if (-not (Test-Path -LiteralPath $defaultProviderPolicyPath)) {
|
||||
New-Item -Path $defaultProviderPolicyPath -Force | Out-Null
|
||||
}
|
||||
@@ -344,6 +349,9 @@ catch {
|
||||
LastSignedInUserHidden = (Get-ItemPropertyValue `
|
||||
-LiteralPath $interactiveLogonPolicyPath `
|
||||
-Name DontDisplayLastUserName) -eq 1
|
||||
FastUserSwitchingHidden = (Get-ItemPropertyValue `
|
||||
-LiteralPath $interactiveLogonPolicyPath `
|
||||
-Name HideFastUserSwitching) -eq 1
|
||||
LocalUserEnumerationDisabled = (Get-ItemPropertyValue `
|
||||
-LiteralPath $defaultProviderPolicyPath `
|
||||
-Name EnumerateLocalUsers) -eq 0
|
||||
|
||||
@@ -201,6 +201,7 @@ $serverScripts = @(
|
||||
'Get-SguUsageReport.ps1',
|
||||
'Get-SguBrokerLog.ps1',
|
||||
'Install-SguDomainMonitoring.ps1',
|
||||
'Publish-SguFsLogixClientDeployment.ps1',
|
||||
'Install-SguRustDeskClient.ps1',
|
||||
'Install-SguRustDeskLinuxEnrollment.ps1',
|
||||
'Install-SguRustDeskServer.ps1',
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
#Requires -Version 5.1
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[string]$GpoName = 'SGU - FSLogix client deployment',
|
||||
[string]$LaboratoryOuDn,
|
||||
[uri]$InstallerZipUri = 'https://download.microsoft.com/download/ae6d2014-e692-45fa-a88b-ee552567cdc1/FSLogix_26.08.zip',
|
||||
[string]$ExpectedInstallerSha256 = '0BAF7FE8195571060F0361B351E58FE6CD422AE521A96BAB3E66D900544E8A30',
|
||||
[string]$CacheRoot = "$env:ProgramData\SGU\FSLogixDeployment\26.08"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$identity = [Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = [Security.Principal.WindowsPrincipal]::new($identity)
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'Run the FSLogix GPO deployment from an elevated Windows PowerShell session.'
|
||||
}
|
||||
Import-Module ActiveDirectory -ErrorAction Stop
|
||||
Import-Module GroupPolicy -ErrorAction Stop
|
||||
|
||||
$domain = Get-ADDomain
|
||||
$domainName = $domain.DNSRoot
|
||||
$domainDn = $domain.DistinguishedName
|
||||
if (-not $LaboratoryOuDn) { $LaboratoryOuDn = "OU=Laboratorio,$domainDn" }
|
||||
Get-ADOrganizationalUnit -Identity $LaboratoryOuDn -ErrorAction Stop | Out-Null
|
||||
|
||||
$zipPath = Join-Path $CacheRoot 'FSLogix_26.08.zip'
|
||||
$extractRoot = Join-Path $CacheRoot 'expanded'
|
||||
$installerPath = Join-Path $extractRoot 'x64\Release\FSLogixAppsSetup.exe'
|
||||
if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) {
|
||||
if (-not $PSCmdlet.ShouldProcess($InstallerZipUri.AbsoluteUri, "Download official FSLogix package to $zipPath")) { return }
|
||||
New-Item -ItemType Directory -Path $CacheRoot,$extractRoot -Force | Out-Null
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
Invoke-WebRequest -Uri $InstallerZipUri -OutFile $zipPath -UseBasicParsing
|
||||
Expand-Archive -LiteralPath $zipPath -DestinationPath $extractRoot -Force
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $installerPath -PathType Leaf)) {
|
||||
throw "The FSLogix x64 installer is missing after extraction: $installerPath"
|
||||
}
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $installerPath
|
||||
if ($signature.Status -ne [Management.Automation.SignatureStatus]::Valid -or
|
||||
-not $signature.SignerCertificate -or
|
||||
$signature.SignerCertificate.Subject -notmatch '(^|,\s*)CN=Microsoft Corporation(,|$)') {
|
||||
throw 'FSLogixAppsSetup.exe does not have a valid Microsoft Corporation signature.'
|
||||
}
|
||||
$installerHash = (Get-FileHash -LiteralPath $installerPath -Algorithm SHA256).Hash
|
||||
if ($installerHash -ne $ExpectedInstallerSha256) {
|
||||
throw "FSLogixAppsSetup.exe SHA-256 mismatch. Expected $ExpectedInstallerSha256; received $installerHash."
|
||||
}
|
||||
|
||||
$gpo = Get-GPO -Name $GpoName -Domain $domainName -ErrorAction SilentlyContinue
|
||||
if (-not $gpo) { $gpo = New-GPO -Name $GpoName -Domain $domainName }
|
||||
$links = @(Get-GPInheritance -Target $LaboratoryOuDn -Domain $domainName).GpoLinks
|
||||
if (-not ($links | Where-Object DisplayName -eq $GpoName)) {
|
||||
New-GPLink -Name $GpoName -Target $LaboratoryOuDn -Domain $domainName -LinkEnabled Yes | Out-Null
|
||||
}
|
||||
|
||||
# Set one ordinary machine value through the supported cmdlet. Besides providing
|
||||
# a deployment marker, this initializes the computer half of the GPO and its
|
||||
# Registry client-side extension before the Scripts extension is added below.
|
||||
Set-GPRegistryValue -Name $GpoName -Domain $domainName `
|
||||
-Key 'HKLM\SOFTWARE\SGU\FSLogixDeployment' -ValueName 'Version' `
|
||||
-Type String -Value '26.08-3.26.826.17182' | Out-Null
|
||||
$gpo = Get-GPO -Name $GpoName -Domain $domainName
|
||||
$guid = $gpo.Id.ToString('B').ToUpperInvariant()
|
||||
$gpoRoot = "\\$domainName\SYSVOL\$domainName\Policies\$guid"
|
||||
$scriptsRoot = Join-Path $gpoRoot 'Machine\Scripts'
|
||||
$startupRoot = Join-Path $scriptsRoot 'Startup'
|
||||
New-Item -ItemType Directory -Path $startupRoot -Force | Out-Null
|
||||
Copy-Item -LiteralPath $installerPath -Destination (Join-Path $startupRoot 'FSLogixAppsSetup.exe') -Force
|
||||
|
||||
$startupPowerShell = @'
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$appsRoot = Join-Path $env:ProgramFiles 'FSLogix\Apps'
|
||||
$frx = Join-Path $appsRoot 'frx.exe'
|
||||
if ((Get-Service frxsvc -ErrorAction SilentlyContinue) -and (Test-Path -LiteralPath $frx)) { exit 0 }
|
||||
$source = Join-Path $PSScriptRoot 'FSLogixAppsSetup.exe'
|
||||
$signature = Get-AuthenticodeSignature -LiteralPath $source
|
||||
if ($signature.Status -ne 'Valid' -or -not $signature.SignerCertificate -or
|
||||
$signature.SignerCertificate.Subject -notmatch '(^|,\s*)CN=Microsoft Corporation(,|$)') { exit 10 }
|
||||
$targetRoot = Join-Path $env:ProgramData 'SGU\FSLogix'
|
||||
New-Item -ItemType Directory -Path $targetRoot -Force | Out-Null
|
||||
$target = Join-Path $targetRoot 'FSLogixAppsSetup.exe'
|
||||
Copy-Item -LiteralPath $source -Destination $target -Force
|
||||
$log = Join-Path $targetRoot 'install.log'
|
||||
$process = Start-Process -FilePath $target -ArgumentList @('/install','/quiet','/norestart','/log',"`"$log`"") -Wait -PassThru
|
||||
if ($process.ExitCode -notin @(0,1641,3010)) { exit $process.ExitCode }
|
||||
if (-not (Get-Service frxsvc -ErrorAction SilentlyContinue) -or -not (Test-Path -LiteralPath $frx)) { exit 11 }
|
||||
exit 0
|
||||
'@
|
||||
[IO.File]::WriteAllText(
|
||||
(Join-Path $startupRoot 'Install-SguFsLogix-Startup.ps1'),
|
||||
$startupPowerShell,
|
||||
[Text.UTF8Encoding]::new($true))
|
||||
$startupCommand = '@echo off' + [Environment]::NewLine +
|
||||
'powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0Install-SguFsLogix-Startup.ps1"' + [Environment]::NewLine +
|
||||
'exit /b %ERRORLEVEL%' + [Environment]::NewLine
|
||||
[IO.File]::WriteAllText(
|
||||
(Join-Path $startupRoot 'Install-SguFsLogix.cmd'),
|
||||
$startupCommand,
|
||||
[Text.Encoding]::ASCII)
|
||||
$scriptsIni = "[Startup]`r`n0CmdLine=Install-SguFsLogix.cmd`r`n0Parameters=`r`n"
|
||||
[IO.File]::WriteAllText(
|
||||
(Join-Path $scriptsRoot 'scripts.ini'),
|
||||
$scriptsIni,
|
||||
[Text.Encoding]::Unicode)
|
||||
|
||||
$policyDn = "CN=$guid,CN=Policies,CN=System,$domainDn"
|
||||
$policy = Get-ADObject -Identity $policyDn -Properties versionNumber,gPCMachineExtensionNames
|
||||
$scriptExtension = '[{42B5FAAE-6536-11D2-AE5A-0000F87571E3}{40B6664F-4972-11D1-A7CA-0000F87571E3}]'
|
||||
$extensions = @([regex]::Matches([string]$policy.gPCMachineExtensionNames, '\[\{[0-9A-Fa-f-]{36}\}\{[0-9A-Fa-f-]{36}\}\]') |
|
||||
ForEach-Object Value)
|
||||
if ($extensions -notcontains $scriptExtension) { $extensions += $scriptExtension }
|
||||
$extensionValue = ($extensions | Sort-Object { $_.Substring(1, 38) }) -join ''
|
||||
$currentVersion = [int64]$policy.versionNumber
|
||||
$machineVersion = $currentVersion -band 0xFFFF
|
||||
if ($machineVersion -ge 65535) { throw 'The computer GPO version cannot be incremented further.' }
|
||||
$newVersion = ($currentVersion -band 0xFFFF0000) -bor ($machineVersion + 1)
|
||||
$gptIniPath = Join-Path $gpoRoot 'gpt.ini'
|
||||
$gptLines = @(Get-Content -LiteralPath $gptIniPath)
|
||||
$versionFound = $false
|
||||
$gptLines = @($gptLines | ForEach-Object {
|
||||
if ($_ -match '^Version=') { $versionFound = $true; "Version=$newVersion" } else { $_ }
|
||||
})
|
||||
if (-not $versionFound) { $gptLines += "Version=$newVersion" }
|
||||
[IO.File]::WriteAllLines($gptIniPath, $gptLines, [Text.Encoding]::ASCII)
|
||||
Set-ADObject -Identity $policy -Replace @{
|
||||
gPCMachineExtensionNames = $extensionValue
|
||||
versionNumber = [int]$newVersion
|
||||
}
|
||||
|
||||
$verifiedGpo = Get-GPO -Name $GpoName -Domain $domainName
|
||||
[pscustomobject]@{
|
||||
GpoName = $verifiedGpo.DisplayName
|
||||
GpoId = $verifiedGpo.Id
|
||||
LaboratoryOuDn = $LaboratoryOuDn
|
||||
MachineVersion = $verifiedGpo.Computer.DSVersion
|
||||
InstallerVersion = '3.26.826.17182'
|
||||
InstallerSha256 = $installerHash
|
||||
StartupScript = Join-Path $startupRoot 'Install-SguFsLogix.cmd'
|
||||
}
|
||||
@@ -99,6 +99,7 @@ $policies = @(
|
||||
@{ Key = $credentialProviderPolicyKey; Name = 'DefaultCredentialProvider'; Type = 'String'; Value = $providerClassId },
|
||||
@{ Key = $credentialProviderPolicyKey; Name = 'EnumerateLocalUsers'; Type = 'DWord'; Value = 0 },
|
||||
@{ Key = $interactiveLogonPolicyKey; Name = 'DontDisplayLastUserName'; Type = 'DWord'; Value = 1 },
|
||||
@{ Key = $interactiveLogonPolicyKey; Name = 'HideFastUserSwitching'; Type = 'DWord'; Value = 1 },
|
||||
|
||||
# Use Windows' native default account image for named user tiles. LogonUI
|
||||
# retains ownership of the anonymous Other user tile and its circular mask.
|
||||
|
||||
@@ -92,6 +92,20 @@ if (-not $lastSignedInUserHidden) {
|
||||
$issues.Add('The last signed-in user is not hidden from LogonUI.')
|
||||
}
|
||||
|
||||
$fastUserSwitchingHidden = $false
|
||||
try {
|
||||
$fastUserSwitchingHidden = (Get-ItemPropertyValue `
|
||||
-LiteralPath $interactiveLogonPolicyPath `
|
||||
-Name HideFastUserSwitching `
|
||||
-ErrorAction Stop) -eq 1
|
||||
}
|
||||
catch {
|
||||
# Report the missing or unreadable policy as a failed enrollment check.
|
||||
}
|
||||
if (-not $fastUserSwitchingHidden) {
|
||||
$issues.Add('Fast User Switching entry points are not hidden.')
|
||||
}
|
||||
|
||||
$localUserEnumerationDisabled = $false
|
||||
try {
|
||||
$localUserEnumerationDisabled = (Get-ItemPropertyValue `
|
||||
@@ -279,6 +293,7 @@ $result = [pscustomobject]@{
|
||||
ProviderBinaryPresent = [bool]$providerBinaryPresent
|
||||
DefaultProviderConfigured = $defaultProviderConfigured
|
||||
LastSignedInUserHidden = $lastSignedInUserHidden
|
||||
FastUserSwitchingHidden = $fastUserSwitchingHidden
|
||||
LocalUserEnumerationDisabled = $localUserEnumerationDisabled
|
||||
PasswordProviderPreserved = $passwordProviderPreserved
|
||||
StandardLocalUserPresent = $standardLocalUserPresent
|
||||
|
||||
@@ -4,6 +4,8 @@ $enrollmentTestScriptPath = Join-Path $repositoryRoot 'scripts\Test-SguClientEnr
|
||||
$packageScriptPath = Join-Path $repositoryRoot 'scripts\New-SguBootstrapPackages.ps1'
|
||||
$releaseScriptPath = Join-Path $repositoryRoot 'scripts\Publish-GiteaRelease.ps1'
|
||||
$azureLauncherPath = Join-Path $repositoryRoot 'scripts\Start-SguAzureClientEnrollment.cmd'
|
||||
$credentialProviderInstallerPath = Join-Path $repositoryRoot 'scripts\Install-CredentialProvider.ps1'
|
||||
$computerPolicyScriptPath = Join-Path $repositoryRoot 'scripts\Set-SguDomainComputerPolicies.ps1'
|
||||
|
||||
$tokens = $null
|
||||
$parseErrors = $null
|
||||
@@ -72,4 +74,13 @@ Describe 'SGU Windows client enrollment scripts' {
|
||||
Should Be $true
|
||||
$azureLauncher | Should Match '-PauseOnError'
|
||||
}
|
||||
|
||||
It 'hides Fast User Switching during enrollment and through computer policy' {
|
||||
(Get-Content -LiteralPath $credentialProviderInstallerPath -Raw) |
|
||||
Should Match 'HideFastUserSwitching'
|
||||
(Get-Content -LiteralPath $enrollmentTestScriptPath -Raw) |
|
||||
Should Match 'FastUserSwitchingHidden'
|
||||
(Get-Content -LiteralPath $computerPolicyScriptPath -Raw) |
|
||||
Should Match "Name = 'HideFastUserSwitching'; Type = 'DWord'; Value = 1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ $bicepPath = Join-Path $repositoryRoot 'infra\azure\main.bicep'
|
||||
$deploymentPath = Join-Path $repositoryRoot 'scripts\Deploy-SguAzureInfrastructure.ps1'
|
||||
$configurationPath = Join-Path $repositoryRoot 'scripts\Enable-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'
|
||||
@@ -11,6 +12,7 @@ foreach ($scriptPath in @(
|
||||
$deploymentPath,
|
||||
$configurationPath,
|
||||
$installerPath,
|
||||
$gpoDeploymentPath,
|
||||
$clientBootstrapPath,
|
||||
$packagePath)) {
|
||||
$tokens = $null
|
||||
@@ -77,6 +79,16 @@ Describe 'SGU Azure user-roaming infrastructure' {
|
||||
}
|
||||
|
||||
Describe 'SGU role-specific roaming policies' {
|
||||
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 an AD computer identity with AES-256 Kerberos' {
|
||||
$source = Get-Content -LiteralPath $configurationPath -Raw
|
||||
$source | Should Match "DomainAccountType = 'ComputerAccount'"
|
||||
@@ -90,6 +102,9 @@ Describe 'SGU role-specific roaming policies' {
|
||||
$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' {
|
||||
@@ -118,11 +133,11 @@ Describe 'SGU role-specific roaming policies' {
|
||||
|
||||
It 'derives valid deterministic AD names for long storage account names' {
|
||||
$name = Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx'
|
||||
$name.Length | Should Be 20
|
||||
$name | Should Match '^sgufs[0-9a-f]{15}$'
|
||||
$name.Length | Should Be 15
|
||||
$name | Should Match '^sgufs[0-9a-f]{10}$'
|
||||
(Get-SguStorageSamAccountName -StorageName 'abcdefghijklmnopqrstuvwx') | Should Be $name
|
||||
(Get-SguStorageSamAccountName -StorageName 'sguroam1234567890123') |
|
||||
Should Be 'sguroam1234567890123'
|
||||
(Get-SguStorageSamAccountName -StorageName 'sguroam1234567') |
|
||||
Should Be 'sguroam1234567'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,5 +164,17 @@ Describe 'SGU FSLogix image enrollment' {
|
||||
$source = Get-Content -LiteralPath $packagePath -Raw
|
||||
$source | Should Match "'Install-SguFsLogix\.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'
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user