Deploy FSLogix automatically through domain policy
This commit is contained in:
@@ -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.
|
||||||
+18
-1
@@ -115,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
|
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.
|
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
|
FSLogix no forma parte de Windows y su licencia debe validarse para las ediciones
|
||||||
y suscripciones usadas. Descargue `FSLogixAppsSetup.exe` directamente de
|
y suscripciones usadas. Descargue `FSLogixAppsSetup.exe` directamente de
|
||||||
@@ -132,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
|
mantener la versión más reciente; FSLogix no se actualiza mediante Windows
|
||||||
Update.
|
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
|
## 4. Validar un piloto
|
||||||
|
|
||||||
Después de `gpupdate /force` y reiniciar un cliente:
|
Después de `gpupdate /force` y reiniciar un cliente:
|
||||||
@@ -141,6 +157,7 @@ Resolve-DnsName "$($azure.UserRoamingStorageAccountName).file.core.windows.net"
|
|||||||
Test-NetConnection "$($azure.UserRoamingStorageAccountName).file.core.windows.net" -Port 445
|
Test-NetConnection "$($azure.UserRoamingStorageAccountName).file.core.windows.net" -Port 445
|
||||||
Get-Service frxsvc
|
Get-Service frxsvc
|
||||||
Get-ItemProperty 'HKLM:\SOFTWARE\FSLogix\Profiles'
|
Get-ItemProperty 'HKLM:\SOFTWARE\FSLogix\Profiles'
|
||||||
|
Get-ItemProperty 'HKLM:\SOFTWARE\SGU\FSLogixDeployment'
|
||||||
& 'C:\Program Files\FSLogix\Apps\frx.exe' list-redirects
|
& 'C:\Program Files\FSLogix\Apps\frx.exe' list-redirects
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -201,6 +201,7 @@ $serverScripts = @(
|
|||||||
'Get-SguUsageReport.ps1',
|
'Get-SguUsageReport.ps1',
|
||||||
'Get-SguBrokerLog.ps1',
|
'Get-SguBrokerLog.ps1',
|
||||||
'Install-SguDomainMonitoring.ps1',
|
'Install-SguDomainMonitoring.ps1',
|
||||||
|
'Publish-SguFsLogixClientDeployment.ps1',
|
||||||
'Install-SguRustDeskClient.ps1',
|
'Install-SguRustDeskClient.ps1',
|
||||||
'Install-SguRustDeskLinuxEnrollment.ps1',
|
'Install-SguRustDeskLinuxEnrollment.ps1',
|
||||||
'Install-SguRustDeskServer.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'
|
$deploymentPath = Join-Path $repositoryRoot 'scripts\Deploy-SguAzureInfrastructure.ps1'
|
||||||
$configurationPath = Join-Path $repositoryRoot 'scripts\Enable-SguAzureUserRoaming.ps1'
|
$configurationPath = Join-Path $repositoryRoot 'scripts\Enable-SguAzureUserRoaming.ps1'
|
||||||
$installerPath = Join-Path $repositoryRoot 'scripts\Install-SguFsLogix.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'
|
$clientBootstrapPath = Join-Path $repositoryRoot 'scripts\Invoke-SguClientBootstrap.ps1'
|
||||||
$azureLauncherPath = Join-Path $repositoryRoot 'scripts\Start-SguAzureClientEnrollment.cmd'
|
$azureLauncherPath = Join-Path $repositoryRoot 'scripts\Start-SguAzureClientEnrollment.cmd'
|
||||||
$packagePath = Join-Path $repositoryRoot 'scripts\New-SguBootstrapPackages.ps1'
|
$packagePath = Join-Path $repositoryRoot 'scripts\New-SguBootstrapPackages.ps1'
|
||||||
@@ -11,6 +12,7 @@ foreach ($scriptPath in @(
|
|||||||
$deploymentPath,
|
$deploymentPath,
|
||||||
$configurationPath,
|
$configurationPath,
|
||||||
$installerPath,
|
$installerPath,
|
||||||
|
$gpoDeploymentPath,
|
||||||
$clientBootstrapPath,
|
$clientBootstrapPath,
|
||||||
$packagePath)) {
|
$packagePath)) {
|
||||||
$tokens = $null
|
$tokens = $null
|
||||||
@@ -162,5 +164,17 @@ Describe 'SGU FSLogix image enrollment' {
|
|||||||
$source = Get-Content -LiteralPath $packagePath -Raw
|
$source = Get-Content -LiteralPath $packagePath -Raw
|
||||||
$source | Should Match "'Install-SguFsLogix\.ps1'"
|
$source | Should Match "'Install-SguFsLogix\.ps1'"
|
||||||
$source | Should Match "'Enable-SguAzureUserRoaming\.ps1'"
|
$source | Should Match "'Enable-SguAzureUserRoaming\.ps1'"
|
||||||
|
$source | Should Match "'Publish-SguFsLogixClientDeployment\.ps1'"
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'publishes a signed idempotent FSLogix computer startup deployment' {
|
||||||
|
$source = Get-Content -LiteralPath $gpoDeploymentPath -Raw
|
||||||
|
$source | Should Match 'Get-AuthenticodeSignature'
|
||||||
|
$source | Should Match 'ExpectedInstallerSha256'
|
||||||
|
$source | Should Match "'Machine\\Scripts'"
|
||||||
|
$source | Should Match "'Startup'"
|
||||||
|
$source | Should Match 'scripts\.ini'
|
||||||
|
$source | Should Match '42B5FAAE-6536-11D2-AE5A-0000F87571E3'
|
||||||
|
$source | Should Match 'Install-SguFsLogix-Startup\.ps1'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user