rds-update-certificate/Scripts/Script.ps1 (302 lines of code) (raw):
[cmdletbinding()]
param(
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$appId,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$appPassword,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$tenantId,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$vaultName,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$secretName,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$adminUsername,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$adminPassword,
[parameter(mandatory = $true)][ValidateNotNullOrEmpty()] [string]$adDomainName,
[Parameter(ValueFromRemainingArguments = $true)]
$extraParameters
)
function log
{
param([string]$message)
"`n`n$(get-date -f o) $message"
}
log "script running..."
whoami
# $PSBoundParameters
if ($extraParameters)
{
log "any extra parameters:"
$extraParameters
}
# determine broker info
#
$brokerInstalled = (Get-WindowsFeature -Name RDS-Connection-Broker).Installed
if (-not $brokerInstalled)
{
log "error: this machine is NOT a broker! exiting. computer: '$env:COMPUTERNAME'"
Get-WindowsFeature | Where-Object Installed -eq $true
exit 1
}
# requires WMF 5.0
# verify NuGet package
#
$nuget = get-packageprovider nuget -Force
if (-not $nuget -or ($nuget.Version -lt 2.8.5.22))
{
log "installing nuget package..."
install-packageprovider -name NuGet -minimumversion 2.8.5.201 -force
}
# install AzureRM module
# min need AzureRM.profile, AzureRM.KeyVault
#
if (-not (get-module AzureRM -ListAvailable))
{
log "installing AzureRm powershell module..."
install-module AzureRM -force
}
# log onto azure account
#
log "logging onto azure account with app id = $appId ..."
$creds = new-object System.Management.Automation.PSCredential ($appId, (convertto-securestring $appPassword -asplaintext -force))
login-azurermaccount -credential $creds -serviceprincipal -tenantid $tenantId -confirm:$false
# get the secret from key vault
#
log "getting secret '$secretName' from keyvault '$vaultName'..."
$secret = get-azurekeyvaultsecret -vaultname $vaultName -name $secretName
$certCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
$bytes = [System.Convert]::FromBase64String($secret.SecretValueText)
$certCollection.Import($bytes, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
add-type -AssemblyName System.Web
$password = [System.Web.Security.Membership]::GeneratePassword(38, 5)
$protectedCertificateBytes = $certCollection.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pkcs12, $password)
$pfxFilePath = join-path $env:TEMP "$([guid]::NewGuid()).pfx"
log "writing the cert as '$pfxFilePath'..."
[io.file]::WriteAllBytes($pfxFilePath, $protectedCertificateBytes)
# get cert info
#
$selfsigned = $false
$wildcard = $false
$cert = $null
$foundcert = $false
$san = $false
# look for enhanced key usage having 'server authentication' and ca false
#
foreach ($cert in $certCollection)
{
if (!($cert.Extensions.CertificateAuthority) -and $cert.EnhancedKeyUsageList -imatch "Server Authentication")
{
$foundcert = $true
break
}
}
if ($foundcert -and $cert)
{
$certSubject = $cert.Subject.Replace("CN=", "").Split(",")[0]
# see if trusted or non-trusted
#
if ($cert.Subject -ieq $cert.Issuer)
{
$selfsigned = $true
}
log "cert '$certSubject' is self-signed:'$selfsigned'"
if ($certSubject.StartsWith("*"))
{
$wildcard = $true
}
log "cert '$certSubject' is wildcard:'$wildcard'"
if ($cert.DnsNameList.Count -gt 1)
{
$san = $true
}
log "cert '$certSubject' is SAN cert:'$san'"
}
else
{
log "unable to find cert $pfxFilePath. exiting..."
exit 1
}
# load RD module
#
Import-Module remotedesktop -DisableNameChecking
# impersonate as admin
# from .\New-ImpersonateUser.ps1 in gallery https://gallery.technet.microsoft.com/scriptcenter/Impersonate-a-User-9bfeff82
#
$ImpersonatedUser = @{}
log "impersonating as '$adminUsername'..."
Add-Type -Namespace Import -Name Win32 -MemberDefinition @'
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool LogonUser(string user, string domain, string password, int logonType, int logonProvider, out IntPtr token);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr handle);
'@
$tokenHandle = 0
$returnValue = [Import.Win32]::LogonUser($adminUserName, $adDomainName, $adminPassword, 2, 0, [ref]$tokenHandle)
if (!$returnValue)
{
$errCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error();
log "failed a call to LogonUser with error code: $errCode"
throw [System.ComponentModel.Win32Exception]$errCode
}
else
{
$ImpersonatedUser.ImpersonationContext = [System.Security.Principal.WindowsIdentity]::Impersonate($tokenHandle)
[void][Import.Win32]::CloseHandle($tokenHandle)
log "impersonating user $([System.Security.Principal.WindowsIdentity]::GetCurrent().Name) returnValue: '$returnValue'"
}
whoami
# set client access name
#
$gatewayConfig = get-rddeploymentgatewayconfiguration
$gatewayConfig | format-list *
(get-wmiobject -Namespace root\cimv2\terminalservices -Class Win32_RDCentralPublishedRemoteDesktop).RDPFileContents
if ($gatewayConfig -and $gatewayConfig.GatewayExternalFqdn)
{
$gatewayExternalFqdn = $gatewayConfig.GatewayExternalFqdn
log "gateway external fqdn: '$gatewayExternalFqdn'"
$externalDomainSuffix = $gatewayExternalFqdn.substring($gatewayExternalFqdn.IndexOf('.') + 1)
log "gateway external domain suffix: '$externalDomainSuffix'"
$externalDomainName = $gatewayExternalFqdn.substring(0, $gatewayExternalFqdn.IndexOf('.'))
log "gateway external domain name: '$externalDomainName'"
$haDeployment = $false
if (Get-RDConnectionBrokerHighAvailability)
{
$haDeployment = $true
}
log "deployment is HA? '$haDeployment'"
$brokerInternalDomainSuffix = ([System.Net.Dns]::GetHostByName($env:COMPUTERNAME).HostName) -ireplace "$($env:COMPUTERNAME).", ""
log "broker internal domain suffix: '$brokerInternalDomainSuffix'"
$localIpAddresses = (Get-NetIPAddress).IPAddress
log "local ip addresses $localIpAddresses"
$certSuffix = $certSubject -ireplace "^.+?\."
$wmi = new-object Management.ManagementClass "\\.\root\cimv2\rdms:Win32_RDMSDeploymentSettings"
# if one of the local ip addresses matches dns lookup for 'clientaccessname'.%certSubject%
# then modify clientaccessname suffix to match external suffix for sso
# if it does not match, do not modify as it will break connection
#
if ($haDeployment)
{
$oldClientAccessName = (Get-RDConnectionBrokerHighAvailability).ClientAccessName
}
else
{
$oldClientAccessName = $wmi.GetStringProperty("DeploymentRedirectorServer").Value
}
log "current client access name: '$oldClientAccessName'"
$newClientAccessNamePrefix = $oldClientAccessName -ireplace "\..*$"
# default value
$clientAccessName = $certSubject
# check dns to see if oldClientAccessNamePrefix with external domain suffix from cert resolves to one of the ips on broker
# if so, change client access name
# this requires a wildcard or san cert
#
$newClientAccessName = "$($newClientAccessNamePrefix).$($certSuffix)"
if ($wildcard -or ($san -and $cert.DnsNameList.Unicode -imatch $newClientAccessName))
{
log "querying dns for possible clientaccessname $newClientAccessName"
$dnsResolve = (Resolve-DnsName -Name $newClientAccessName -ErrorAction SilentlyContinue).IPAddress
log "dns addresses $dnsResolve"
if ($localIpAddresses -and $dnsResolve)
{
$notMatchedCount = (Compare-Object -ReferenceObject $localIpAddresses -DifferenceObject $dnsResolve).Count
if (($dnsResolve.Count + $localIpAddresses.Count - 2) -le $notMatchedCount)
{
log "local address was found in dns query using external suffix"
$clientAccessName = $newClientAccessName
$continue = $true
# update rap on all gateways to include new client access name
#
foreach ($server in (Get-RDServer -Role RDS-GATEWAY).Server)
{
$wmi = Invoke-Command -ComputerName $server -ScriptBlock `
{
Get-WmiObject -Namespace root\cimv2\terminalservices -Class Win32_TSGatewayResourceGroup | Where-Object Name -eq RDG_DNSRoundRobin
}
log $wmi
if ($wmi -and $wmi.Resources -inotmatch $clientAccessName)
{
$ret = Invoke-Command -ComputerName $server -ScriptBlock `
{
(Get-WmiObject -Namespace root\cimv2\terminalservices -Class Win32_TSGatewayResourceGroup | Where-Object Name -eq RDG_DNSRoundRobin).InvokeMethod("AddResources", $clientAccessName)
}
log $ret
if ($ret -eq 0)
{
log "updated rap on gateway $server"
}
else
{
log "unable to update rap on gateway $server"
$continue = $false
}
}
elseif (!$wmi)
{
log "unable to connect to gateway $server"
$continue = $false
}
else
{
log "gateway $server rap already contains $clientAccessName"
}
# dont error but dont continue if above fails
#
if (!$continue)
{
break
}
} # end for
if ($haDeployment)
{
log "setting client access name for ha sso"
$ret = Set-RDClientAccessName -ClientAccessName $clientAccessName
}
else
{
log "setting client access name for sso"
# from https://gallery.technet.microsoft.com/Change-published-FQDN-for-2a029b80
$ret = $wmi.SetStringProperty("DeploymentRedirectorServer", $clientAccessName).ReturnValue
}
} # end if dns match
} # end if ips resolved
} # end if wildcard or san check
log "setting new client access name to '$clientAccessName'..."
if ($wildcard)
{
$gatewayExternalFQDN = $externalDomainName + '.' + $certSuffix
}
else
{
$gatewayExternalFqdn = $certSubject
}
log "setting gateway external FQDN to '$gatewayExternalFqdn'..."
$gatewayConfig = get-rddeploymentgatewayconfiguration
$gatewayConfig | format-list *
$ret = Set-RDDeploymentGatewayConfiguration -GatewayMode "Custom" `
-GatewayExternalFqdn $gatewayExternalFqdn `
-LogonMethod $gatewayConfig.LogonMethod `
-UseCachedCredentials $gatewayConfig.UseCachedCredentials `
-BypassLocal $gatewayConfig.BypassLocal `
-Force
$gatewayConfig = get-rddeploymentgatewayconfiguration
$gatewayConfig | format-list *
}
# apply certificate
#
foreach ($role in @("RDGateway", "RDWebAccess", "RDRedirector", "RDPublishing"))
{
log "applying certificate for role: $role ..."
set-rdcertificate -role $role -importpath $pfxFilePath -password (convertto-securestring $password -asplaintext -force) -force
}
$gatewayConfig
(get-wmiobject -Namespace root\cimv2\terminalservices -Class Win32_RDCentralPublishedRemoteDesktop).RDPFileContents
log "remove impersonation..."
$ImpersonatedUser.ImpersonationContext.Undo()
whoami
# clean up
#
if (test-path($pfxFilePath))
{
log "running cleanup..."
remove-item $pfxFilePath
}
log "done."
if (!$clientAccessName)
{
exit 1
}