Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c47913e81d | ||
|
|
da310e4213 |
@@ -111,7 +111,9 @@ For a public Azure VM, use
|
||||
optional Azure P2S gateway or direct enrollment restricted to explicit public
|
||||
source CIDRs. P2S deployments also provision private Azure Files storage for
|
||||
role-based roaming: `AL` redirects only Documents/Desktop, while `AD` and `DO`
|
||||
can mount an FSLogix VHDX profile after the post-domain setup step.
|
||||
use an FSLogix VHDX profile after the post-domain setup step. Existing public
|
||||
Azure Files deployments automatically use FSLogix Cloud Cache locally instead
|
||||
of performing all profile I/O across WAN SMB.
|
||||
|
||||
Never disable the built-in Microsoft password Credential Provider. It is the
|
||||
supported recovery path if a third-party provider fails to load.
|
||||
|
||||
@@ -84,7 +84,9 @@ Azure P2S deployments can add a dedicated, private Azure Files account. Student
|
||||
accounts (`AL`) keep a non-authoritative local profile and redirect only
|
||||
Documents/Desktop. Administrative and professor accounts (`AD`/`DO`) receive an
|
||||
FSLogix VHDX profile container through object-specific settings keyed to their
|
||||
AD security-group SIDs. The storage account uses AD DS Kerberos with AES-256;
|
||||
AD security-group SIDs. Private endpoints mount the VHDX directly; public WAN
|
||||
endpoints use a local FSLogix Cloud Cache synchronized to Azure Files so profile
|
||||
I/O does not block on every SMB round trip. The storage account uses AD DS Kerberos with AES-256;
|
||||
share-root ACLs use `CREATOR OWNER` so users cannot browse one another's data.
|
||||
The bootstrap staging account is deliberately separate. See
|
||||
[user-roaming.md](user-roaming.md) for rollout and recovery constraints.
|
||||
|
||||
@@ -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.
|
||||
@@ -0,0 +1,42 @@
|
||||
# SGU Credential Provider 0.6.3
|
||||
|
||||
Corrige el primer inicio de sesión de perfiles completos `AD`/`DO` cuando Azure
|
||||
Files se consume mediante su endpoint público.
|
||||
|
||||
## Cambios
|
||||
|
||||
- `Enable-SguAzureUserRoaming.ps1` selecciona automáticamente FSLogix Cloud
|
||||
Cache para endpoints públicos y conserva el montaje directo para endpoints
|
||||
privados.
|
||||
- Cloud Cache realiza el I/O del perfil sobre el VHDX local y sincroniza con
|
||||
Azure Files, evitando que la latencia WAN de SMB bloquee cada operación.
|
||||
- La GPO elimina de forma efectiva `VHDLocations` al activar `CCDLocations`,
|
||||
incluyendo equipos que habían aplicado la política directa anterior.
|
||||
- Se impide el retorno silencioso a un perfil local o temporal cuando FSLogix
|
||||
no puede registrar el contenedor.
|
||||
- La documentación agrega capacidad de disco, validación del registro efectivo
|
||||
y rutas de diagnóstico para ambos modos.
|
||||
|
||||
## Validación del despliegue
|
||||
|
||||
- Se eliminó únicamente el perfil piloto fallido de `AD017045` en `AF-03`; la
|
||||
cuenta de Active Directory se conservó.
|
||||
- La GPO `SGU - AD-DO FSLogix profiles` quedó en Cloud Cache para los SID de
|
||||
`SGU-Administrativos` y `SGU-Docentes`; `VHDLocations` ya no aparece en el
|
||||
registro efectivo de `AF-03`.
|
||||
- El nuevo inicio de `AD017045` creó la caché y el proxy locales, agregó la
|
||||
redirección del perfil al VHDX y completó `LoadProfile` en 4.649 segundos, sin
|
||||
error 258, perfil temporal ni retorno local.
|
||||
- Pasaron 70 pruebas Pester, 41 pruebas de `SGU.AuthBroker.Core`, 16 de
|
||||
`SGU.AuthBroker` y 6 del Credential Provider. La solución compiló sin errores.
|
||||
|
||||
## Descargas
|
||||
|
||||
- `sgu-windows-client-bootstrap-0.6.3.zip`
|
||||
- `sgu-server-bootstrap-0.6.3.zip`
|
||||
- `sgu-linux-client-bootstrap-0.6.3.zip`
|
||||
- `sgu-azure-infrastructure-0.6.3.zip`
|
||||
- `SHA256SUMS-0.6.3.txt`
|
||||
|
||||
Verifique los hashes SHA-256 antes de ejecutar los paquetes. No se incluyen
|
||||
credenciales, claves de almacenamiento ni artefactos temporales del despliegue.
|
||||
+53
-5
@@ -6,7 +6,7 @@ cada tipo de cuenta SGU:
|
||||
| Rol | Datos conservados | Implementación |
|
||||
| --- | --- | --- |
|
||||
| `AL` | `Desktop` y `Documents` | Redirección de carpetas a Azure Files; el resto del perfil local no es autoritativo y no se copia al iniciar o cerrar sesión. |
|
||||
| `AD` / `DO` | Perfil completo | Un contenedor VHDX único de FSLogix montado desde Azure Files. |
|
||||
| `AD` / `DO` | Perfil completo | FSLogix: montaje directo del VHDX con endpoint privado o Cloud Cache local sincronizada con Azure Files cuando el endpoint es público. |
|
||||
|
||||
Linux conserva su solución nativa de directorios personales; no intenta montar
|
||||
un perfil de Windows.
|
||||
@@ -36,6 +36,22 @@ esa ejecución; después se establece `defaultAction=Deny` y se deja únicamente
|
||||
red institucional autorizada. En el despliegue LCI actual esa regla final es
|
||||
`200.13.89.0/24`.
|
||||
|
||||
El modo `-StaffProfileStorageMode Auto` es el predeterminado. Selecciona
|
||||
`Direct` con un endpoint privado y `CloudCache` con uno público. Cloud Cache
|
||||
mantiene el VHDX de trabajo en `C:\ProgramData\FSLogix\Cache` y sincroniza sus
|
||||
cambios con Azure Files, evitando que cada operación del perfil dependa de la
|
||||
latencia WAN de SMB. `CCDLocations` y `VHDLocations` son mutuamente excluyentes:
|
||||
el configurador elimina de forma efectiva el valor obsoleto al cambiar de modo,
|
||||
incluso en clientes que ya habían aplicado la GPO anterior. Puede forzarse un
|
||||
modo con `-StaffProfileStorageMode Direct` o `CloudCache`, pero `Direct` sobre
|
||||
un endpoint público sólo debe usarse para diagnóstico.
|
||||
|
||||
Cloud Cache necesita espacio local suficiente para el perfil (30 GB de máximo
|
||||
por omisión), conserva la caché tras cerrar sesión y exige al menos un proveedor
|
||||
remoto sano al registrar o desmontar el perfil. La GPO también impide el inicio
|
||||
con perfil temporal o local cuando FSLogix falla, para que una incidencia no
|
||||
produzca dos perfiles divergentes silenciosamente.
|
||||
|
||||
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
|
||||
@@ -97,6 +113,14 @@ $parameters = @{
|
||||
.\payload\scripts\Enable-SguAzureUserRoaming.ps1 @parameters
|
||||
```
|
||||
|
||||
Para reutilizar una cuenta con endpoint público:
|
||||
|
||||
```powershell
|
||||
$parameters.EndpointAccess = 'Public'
|
||||
$parameters.StaffProfileStorageMode = 'Auto' # resuelve a CloudCache
|
||||
.\payload\scripts\Enable-SguAzureUserRoaming.ps1 @parameters
|
||||
```
|
||||
|
||||
La operación es idempotente. Crea `OU=AzureFilesConfig`, une la cuenta de
|
||||
almacenamiento al bosque y publica dos GPO:
|
||||
|
||||
@@ -115,7 +139,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
|
||||
@@ -132,6 +156,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:
|
||||
@@ -140,14 +180,21 @@ Después de `gpupdate /force` y reiniciar un cliente:
|
||||
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\FSLogix\Profiles\ObjectSpecific\<SID-del-grupo>'
|
||||
Get-ItemProperty 'HKLM:\SOFTWARE\SGU\FSLogixDeployment'
|
||||
& 'C:\Program Files\FSLogix\Apps\frx.exe' list-redirects
|
||||
```
|
||||
|
||||
La resolución debe devolver la IP privada del endpoint. Inicie primero con un
|
||||
En modo privado, la resolución debe devolver la IP privada del endpoint; en
|
||||
modo público debe devolver una dirección pública y TCP 445 debe estar permitido
|
||||
desde la red institucional. En Cloud Cache compruebe además que existe
|
||||
`C:\ProgramData\FSLogix\Cache` y que el registro efectivo contiene
|
||||
`CCDLocations`, pero no `VHDLocations`. Inicie primero con un
|
||||
`AL` nuevo y confirme las ubicaciones de Escritorio/Documentos; luego pruebe un
|
||||
`AD` o `DO` sin perfil local previo y confirme la creación de un VHDX en
|
||||
`profiles`. No active el borrado de perfiles locales ni despliegue a todos los
|
||||
`profiles`. Revise `%ProgramData%\FSLogix\Logs\Profile` para confirmar que el
|
||||
perfil se registró sin `ERROR: 258`, perfil temporal ni retorno a `C:\Users`.
|
||||
No active el borrado de perfiles locales ni despliegue a todos los
|
||||
usuarios hasta probar inicio, cierre, desconexión de VPN, cuota y restauración.
|
||||
|
||||
Referencias de Microsoft:
|
||||
@@ -156,4 +203,5 @@ Referencias de Microsoft:
|
||||
- [Permisos SMB para FSLogix](https://learn.microsoft.com/fslogix/how-to-configure-storage-permissions)
|
||||
- [Contenedores de perfil FSLogix](https://learn.microsoft.com/fslogix/how-to-configure-profile-containers)
|
||||
- [Configuración ObjectSpecific de FSLogix](https://learn.microsoft.com/fslogix/how-to-configure-object-specific-settings)
|
||||
- [Cloud Cache de FSLogix](https://learn.microsoft.com/fslogix/concepts-fslogix-cloud-cache)
|
||||
- [Redirección de carpetas](https://learn.microsoft.com/windows-server/storage/folder-redirection/deploy-folder-redirection)
|
||||
|
||||
@@ -23,6 +23,8 @@ param(
|
||||
[string]$StaffGpoName = 'SGU - AD-DO FSLogix profiles',
|
||||
[ValidateSet('Private', 'Public')]
|
||||
[string]$EndpointAccess = 'Private',
|
||||
[ValidateSet('Auto', 'Direct', 'CloudCache')]
|
||||
[string]$StaffProfileStorageMode = 'Auto',
|
||||
[ValidateRange(1024, 1048576)]
|
||||
[int]$FsLogixProfileSizeMiB = 30000,
|
||||
[string]$AzFilesHybridModulePath,
|
||||
@@ -161,6 +163,36 @@ function Set-SguGpoRegistryValue {
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-SguGpoRegistryValue {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$GpoName,
|
||||
[Parameter(Mandatory)][string]$DomainName,
|
||||
[Parameter(Mandatory)][string]$Server,
|
||||
[Parameter(Mandatory)][string]$Key,
|
||||
[Parameter(Mandatory)][string]$ValueName
|
||||
)
|
||||
|
||||
if ($PSCmdlet.ShouldProcess("$GpoName :: $Key\$ValueName", 'Remove obsolete policy value')) {
|
||||
Remove-GPRegistryValue -Name $GpoName -Domain $DomainName -Server $Server `
|
||||
-Key $Key -ValueName $ValueName -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Set-SguGpoRegistryValueDeletion {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$GpoName,
|
||||
[Parameter(Mandatory)][string]$DomainName,
|
||||
[Parameter(Mandatory)][string]$Server,
|
||||
[Parameter(Mandatory)][string]$Key,
|
||||
[Parameter(Mandatory)][string]$ValueName
|
||||
)
|
||||
|
||||
Remove-SguGpoRegistryValue -GpoName $GpoName -DomainName $DomainName -Server $Server `
|
||||
-Key $Key -ValueName $ValueName
|
||||
Set-SguGpoRegistryValue -GpoName $GpoName -DomainName $DomainName -Server $Server `
|
||||
-Key $Key -ValueName "**del.$ValueName" -Type String -Value ''
|
||||
}
|
||||
|
||||
function Get-SguUnusedDriveName {
|
||||
$used = @(Get-PSDrive -PSProvider FileSystem | Select-Object -ExpandProperty Name)
|
||||
foreach ($name in @('Z', 'Y', 'X', 'W', 'V')) {
|
||||
@@ -350,6 +382,12 @@ $storageCredential = [PSCredential]::new(
|
||||
(ConvertTo-SecureString -String $storageKey -AsPlainText -Force))
|
||||
$profilesSharePath = "\\$fileEndpointHost\$FsLogixProfilesShareName"
|
||||
$redirectedFoldersSharePath = "\\$fileEndpointHost\$RedirectedFoldersShareName"
|
||||
$resolvedStaffProfileStorageMode = if ($StaffProfileStorageMode -eq 'Auto') {
|
||||
if ($EndpointAccess -eq 'Public') { 'CloudCache' } else { 'Direct' }
|
||||
}
|
||||
else {
|
||||
$StaffProfileStorageMode
|
||||
}
|
||||
$perUserRootRights = [Security.AccessControl.FileSystemRights]::CreateDirectories -bor
|
||||
[Security.AccessControl.FileSystemRights]::ListDirectory -bor
|
||||
[Security.AccessControl.FileSystemRights]::ReadAttributes -bor
|
||||
@@ -407,12 +445,47 @@ $fsLogixValues = [ordered]@{
|
||||
ReAttachIntervalSeconds = @{ Type = 'DWord'; Value = 15 }
|
||||
ReAttachRetryCount = @{ Type = 'DWord'; Value = 3 }
|
||||
SizeInMBs = @{ Type = 'DWord'; Value = $FsLogixProfileSizeMiB }
|
||||
VHDLocations = @{ Type = 'String'; Value = $profilesSharePath }
|
||||
VolumeType = @{ Type = 'String'; Value = 'VHDX' }
|
||||
}
|
||||
$cloudCacheOnlyValues = @(
|
||||
'CCDLocations',
|
||||
'ClearCacheOnLogoff',
|
||||
'HealthyProvidersRequiredForRegister',
|
||||
'HealthyProvidersRequiredForUnregister',
|
||||
'PreventLoginWithFailure',
|
||||
'PreventLoginWithTempProfile',
|
||||
'VHDCompactDisk'
|
||||
)
|
||||
if ($resolvedStaffProfileStorageMode -eq 'CloudCache') {
|
||||
$fsLogixValues.CCDLocations = @{
|
||||
Type = 'String'
|
||||
Value = "type=smb,name=`"SGU Azure Files`",connectionString=$profilesSharePath"
|
||||
}
|
||||
$fsLogixValues.ClearCacheOnLogoff = @{ Type = 'DWord'; Value = 0 }
|
||||
$fsLogixValues.HealthyProvidersRequiredForRegister = @{ Type = 'DWord'; Value = 1 }
|
||||
$fsLogixValues.HealthyProvidersRequiredForUnregister = @{ Type = 'DWord'; Value = 1 }
|
||||
$fsLogixValues.PreventLoginWithFailure = @{ Type = 'DWord'; Value = 1 }
|
||||
$fsLogixValues.PreventLoginWithTempProfile = @{ Type = 'DWord'; Value = 1 }
|
||||
$fsLogixValues.VHDCompactDisk = @{ Type = 'DWord'; Value = 0 }
|
||||
}
|
||||
else {
|
||||
$fsLogixValues.VHDLocations = @{ Type = 'String'; Value = $profilesSharePath }
|
||||
}
|
||||
foreach ($staffGroup in @($professorGroup, $administrativeGroup)) {
|
||||
$objectSpecificKey = "$fsLogixRoot\ObjectSpecific\$($staffGroup.SID.Value)"
|
||||
if ($resolvedStaffProfileStorageMode -eq 'CloudCache') {
|
||||
Set-SguGpoRegistryValueDeletion -GpoName $staffGpo.DisplayName -DomainName $domainName `
|
||||
-Server $DomainController -Key $objectSpecificKey -ValueName 'VHDLocations'
|
||||
}
|
||||
else {
|
||||
foreach ($obsoleteValue in $cloudCacheOnlyValues) {
|
||||
Set-SguGpoRegistryValueDeletion -GpoName $staffGpo.DisplayName -DomainName $domainName `
|
||||
-Server $DomainController -Key $objectSpecificKey -ValueName $obsoleteValue
|
||||
}
|
||||
}
|
||||
foreach ($setting in $fsLogixValues.GetEnumerator()) {
|
||||
Remove-SguGpoRegistryValue -GpoName $staffGpo.DisplayName -DomainName $domainName `
|
||||
-Server $DomainController -Key $objectSpecificKey -ValueName "**del.$($setting.Key)"
|
||||
Set-SguGpoRegistryValue -GpoName $staffGpo.DisplayName -DomainName $domainName `
|
||||
-Server $DomainController -Key $objectSpecificKey -ValueName $setting.Key `
|
||||
-Type $setting.Value.Type -Value $setting.Value.Value
|
||||
@@ -423,6 +496,7 @@ foreach ($staffGroup in @($professorGroup, $administrativeGroup)) {
|
||||
StorageAccountName = $StorageAccountName
|
||||
FileEndpoint = $fileEndpointHost
|
||||
EndpointAccess = $EndpointAccess
|
||||
StaffProfileStorageMode = $resolvedStaffProfileStorageMode
|
||||
EndpointAddresses = $endpointAddresses
|
||||
PrivateEndpointAddresses = $privateAddresses
|
||||
DirectoryService = $directoryService
|
||||
@@ -434,6 +508,11 @@ foreach ($staffGroup in @($professorGroup, $administrativeGroup)) {
|
||||
StudentPolicy = $studentGpo.DisplayName
|
||||
StaffPolicy = $staffGpo.DisplayName
|
||||
StudentBehavior = 'Local non-authoritative profile; Documents and Desktop redirected without Offline Files pinning.'
|
||||
StaffBehavior = 'FSLogix VHDX profile container for SGU-Docentes and SGU-Administrativos only.'
|
||||
StaffBehavior = if ($resolvedStaffProfileStorageMode -eq 'CloudCache') {
|
||||
'FSLogix Cloud Cache profile container for SGU-Docentes and SGU-Administrativos only; local I/O is synchronized to Azure Files.'
|
||||
}
|
||||
else {
|
||||
'Direct FSLogix VHDX profile container for SGU-Docentes and SGU-Administrativos only.'
|
||||
}
|
||||
ExistingStaffLocalProfilesDeleted = [bool]$DeleteExistingStaffLocalProfiles
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
@@ -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
|
||||
@@ -87,6 +89,21 @@ Describe 'SGU role-specific roaming policies' {
|
||||
$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 'PreventLoginWithFailure'
|
||||
$source | Should Match 'VHDCompactDisk'
|
||||
}
|
||||
|
||||
It 'uses an AD computer identity with AES-256 Kerberos' {
|
||||
$source = Get-Content -LiteralPath $configurationPath -Raw
|
||||
$source | Should Match "DomainAccountType = 'ComputerAccount'"
|
||||
@@ -162,5 +179,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