[CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9.-]*$')] [string]$DomainName, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9][A-Za-z0-9.-]*$')] [string]$ServerAddress, [Parameter(Mandatory)] [ValidatePattern('^[A-Za-z0-9+/=]+$')] [string]$ServerPublicKey, [string]$RegistrationShareName = 'SGU-RustDesk-Enrollment$', [string]$DataRoot = "$env:ProgramData\SGU\RustDesk\LinuxEnrollment", [string]$ProcessorScriptPath = (Join-Path $PSScriptRoot 'Invoke-SguRustDeskLinuxRegistrationProcessor.ps1') ) $ErrorActionPreference = 'Stop' function Assert-Administrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'Only a local administrator can install Linux RustDesk enrollment.' } } function Get-EnrollmentCertificate { param([Parameter(Mandatory)][string]$FriendlyName) $certificate = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object FriendlyName -eq $FriendlyName | Where-Object HasPrivateKey | Select-Object -First 1 if (-not $certificate) { $certificate = New-SelfSignedCertificate ` -Subject 'CN=SGU RustDesk Linux enrollment' ` -FriendlyName $FriendlyName ` -CertStoreLocation 'Cert:\LocalMachine\My' ` -KeyAlgorithm RSA ` -KeyLength 3072 ` -KeyUsage KeyEncipherment,DigitalSignature ` -NotAfter (Get-Date).AddYears(5) } return $certificate } function Set-EnrollmentDirectoryAcl { param( [Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$DomainNetbiosName ) New-Item -ItemType Directory -Path $Path -Force | Out-Null $arguments = @( "`"$Path`"", '/inheritance:r', '/grant:r', 'SYSTEM:(OI)(CI)(F)', 'BUILTIN\Administrators:(OI)(CI)(F)', "$DomainNetbiosName\Domain Computers:(OI)(CI)(M)" ) & icacls.exe @arguments | Out-Null if ($LASTEXITCODE -ne 0) { throw "Could not secure the Linux RustDesk enrollment directory $Path." } } Assert-Administrator Import-Module ActiveDirectory -ErrorAction Stop if (-not (Test-Path -LiteralPath $ProcessorScriptPath -PathType Leaf)) { throw "The Linux RustDesk registration processor is missing: $ProcessorScriptPath" } if (-not $PSCmdlet.ShouldProcess($env:COMPUTERNAME, 'Install the protected Linux RustDesk enrollment endpoint')) { return } $domain = Get-ADDomain -Identity $DomainName $publicRoot = Join-Path $DataRoot 'Public' $requestsRoot = Join-Path $publicRoot 'Requests' $archiveRoot = Join-Path $publicRoot 'Archive' $rejectedRoot = Join-Path $publicRoot 'Rejected' foreach ($path in @($DataRoot, $publicRoot, $requestsRoot, $archiveRoot, $rejectedRoot)) { Set-EnrollmentDirectoryAcl -Path $path -DomainNetbiosName $domain.NetBIOSName } $certificate = Get-EnrollmentCertificate -FriendlyName 'SGU RustDesk Linux enrollment' $publicCertificatePath = Join-Path $publicRoot 'registration-public.cer' Export-Certificate -Cert $certificate -FilePath $publicCertificatePath -Force | Out-Null $clientConfiguration = [ordered]@{ ServerAddress = $ServerAddress ServerPublicKey = $ServerPublicKey RegistrationShare = "\\$env:COMPUTERNAME\$RegistrationShareName" UpdatedAt = (Get-Date).ToString('o') } [IO.File]::WriteAllText((Join-Path $publicRoot 'rustdesk-client.json'), ($clientConfiguration | ConvertTo-Json), [Text.UTF8Encoding]::new($false)) $share = Get-SmbShare -Name $RegistrationShareName -ErrorAction SilentlyContinue if (-not $share) { New-SmbShare -Name $RegistrationShareName -Path $publicRoot ` -FullAccess @('SYSTEM', 'BUILTIN\Administrators') ` -ChangeAccess "$($domain.NetBIOSName)\Domain Computers" | Out-Null } elseif ($share.Path -ne $publicRoot) { throw "The existing SMB share $RegistrationShareName points to $($share.Path), not $publicRoot." } $installedProcessor = Join-Path $DataRoot 'Invoke-SguRustDeskLinuxRegistrationProcessor.ps1' Copy-Item -LiteralPath $ProcessorScriptPath -Destination $installedProcessor -Force $processorArguments = "-NoProfile -NonInteractive -ExecutionPolicy Bypass -File `"$installedProcessor`" -DataRoot `"$DataRoot`" -CertificateThumbprint $($certificate.Thumbprint)" $action = New-ScheduledTaskAction -Execute (Join-Path $env:WINDIR 'System32\WindowsPowerShell\v1.0\powershell.exe') ` -Argument $processorArguments $trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) ` -RepetitionInterval (New-TimeSpan -Minutes 1) -RepetitionDuration (New-TimeSpan -Days 3650) $principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest Register-ScheduledTask -TaskName 'SGU-RustDesk-LinuxRegistration' -Action $action -Trigger $trigger ` -Principal $principal -Description 'Registers encrypted RustDesk credentials sent by domain-joined Linux computers.' -Force | Out-Null New-NetFirewallRule -DisplayName 'SGU RustDesk Linux enrollment SMB' -Group 'SGU RustDesk' ` -Direction Inbound -Action Allow -Protocol TCP -LocalPort 445 -Profile Domain -ErrorAction SilentlyContinue | Out-Null [pscustomobject]@{ RegistrationShare = "\\$env:COMPUTERNAME\$RegistrationShareName" PublicCertificatePath = $publicCertificatePath RegistrationTask = 'SGU-RustDesk-LinuxRegistration' CertificateThumbprint = $certificate.Thumbprint }