|
<# |
|
.SYNOPSIS |
|
This script: |
|
Creates a new resource group |
|
Creates a VNet with two subnets in the RG |
|
Allocates a static public IP |
|
Creates a NSG and adds rules permitting TCP 3389, 80 and 443 |
|
Launches a WinSrv2012R2 instance |
|
Configures RDG server via Remote Desktop Services PowerShell provider using a DSC configuration |
|
The DSC configuration: |
|
Is included in this script as a here-string (Watch out for quotes and double quotes!) |
|
Expects the fully-qualified domain name from this script to be passed to it as a parameter |
|
Uses a DSC File resource to create a variable in the configuration since the DSC Script resource does not resolve parameters |
|
|
|
.DESCRIPTION |
|
A single AzureRM script to create a Vnet, launch a VM, connect it to Azure DSC and run a DSC configuration to install and configure Remote Desktop Gateway |
|
|
|
.PARAMETER dnsName |
|
FQDN of the to-be-generated self-signed certificate |
|
|
|
.OUTPUTS |
|
Azure Resource Group, Vnet, networking resources, VM, Azure Automation Account and Azure DSC node configuration |
|
Working Remote Desktop Gateway with self-signed certificate |
|
Self-signed cert at $HOME/desktop/$dnsName.cert |
|
|
|
.NOTES |
|
This script adds non-AD local groups to RD-CAP and permits all accesses to back-end resources |
|
|
|
Alex Neihaus 2017-08-22 |
|
(c) 2017 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0 |
|
Licensed under the Apache License, Version 2.0 (the "License"); |
|
you may not use this file except in compliance with the License. |
|
You may obtain a copy of the License at |
|
http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
Unless required by applicable law or agreed to in writing, software |
|
distributed under the License is distributed on an "AS IS" BASIS, |
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
See the License for the specific language governing permissions and |
|
limitations under the License. |
|
|
|
Author's blog: https://www.yobyot.com |
|
|
|
#> |
|
|
|
$VerbosePreference = "Continue" |
|
|
|
# DSC configuration script follows as a PowerShell "here string". TEST THIS CONFIG BEFORE EMBEDDING. If it has errors, this script will fail. |
|
# In the here-string that follows DO NOT USE any single quotes (') as this will mess up the here string. |
|
# Note that parameters for the DSC configuration are initialized in the script below and passed to Azure DSC via Start-AzureRmAutomationDscCompilationJob |
|
$configText = @' |
|
configuration InstallConfigureRemoteDesktopGateway |
|
{ |
|
param( |
|
[Parameter(Mandatory=$true)] |
|
[string]$FQDN |
|
) |
|
$VerbosePreference = "Continue" |
|
Import-DscResource -ModuleName PSDesiredStateConfiguration |
|
Import-DscResource -Name WindowsFeature |
|
Import-DscResource -Name Script |
|
Node localhost |
|
{ |
|
File LoggingDirectory # Create a directory for log files and for the cert |
|
{ |
|
Ensure = "Present" |
|
DestinationPath = "C:\Temp" |
|
Type = "Directory" |
|
MatchSource = $false # Only create the temp directory the first time the configuration is run |
|
} |
|
WindowsFeatureSet InstallRemoteDesktopGatewayFeatures |
|
{ |
|
Name = @("web-server", "NPAS-Policy-Server", "Web-ISAPI-Ext", "Web-Mgmt-Compat", "RSAT-NPAS", "RPC-over-HTTP-Proxy", "RSAT-RDS-Gateway", "RDS-Gateway", "Telnet-client") |
|
Ensure = "Present" |
|
DependsOn = "[File]LoggingDirectory" |
|
} |
|
File FQDNFile |
|
{ |
|
# Because the DSC Script resources does _not_ accept parameters in its GetScript statement, we will write the compile-time |
|
# FQDN parm to disk on this node and read it in in the Script resource below |
|
DependsOn ="[File]LoggingDirectory" |
|
Ensure = "Present" |
|
Type = "File" |
|
Contents = $FQDN |
|
DestinationPath = "C:\Temp\FQDN.txt" |
|
} |
|
Script "ConfigureRemoteDesktopGateway" |
|
{ |
|
DependsOn = @("[WindowsFeatureSet]InstallRemoteDesktopGatewayFeatures", "[File]LoggingDirectory") |
|
SetScript = { |
|
Start-Transcript -Path "C:\Temp\ConfigureRemoteDesktopGateway.txt" -Append -Force -IncludeInvocationHeader |
|
Import-Module RemoteDesktopServices |
|
# Get the FQDN from the file created in the File resource |
|
# Create a self-signed certificate. This MUST installed in the LocalMachine Trusted Root store for RDP clients to see it. |
|
$DomainName = Get-Content -Path "C:\Temp\FQDN.txt" -Raw |
|
$x509Obj = New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName $DomainName |
|
# Export the cert to the desktop for use on clients |
|
$x509Obj | Export-Certificate -FilePath "C:\Temp\$DomainName.cer" -Force -Type CERT |
|
# See https://blogs.technet.microsoft.com/ptsblog/2011/12/09/extending-remote-desktop-services-using-powershell-part-5/ |
|
# for details of using the RDS provider. Its very poorly documented. If you need to find additional items, sl to the GatewayServer location you are interested in |
|
# and gci . -recurse | fl |
|
# Create RD-CAP with two user groups; defaults permit all device redirection. Might be worth tightening up in terms of security. |
|
$capName = "RD-CAP-$(Get-Date -Format FileDateTimeUniversal)" |
|
Set-Location RDS:\GatewayServer\SSLCertificate #Change to location where self-signed certificate is specified |
|
Set-Item .\Thumbprint -Value $x509obj.Thumbprint # Update RDG with the thumprint of the self-signed cert. |
|
# Create a new Connection Authorization Profile |
|
New-Item -Path RDS:\GatewayServer\CAP -Name $capName -UserGroups @("administrators@BUILTIN"; "Remote Desktop Users@BUILTIN") -AuthMethod 1 |
|
# Create a new Resouce Authorization Profile with "ComputerGroupType" set to 2 to permit connections to any device |
|
$rapName = "RD-RAP-$(Get-Date -Format FileDateTimeUniversal)" |
|
New-Item -Path RDS:\GatewayServer\RAP -Name $rapName -UserGroups @("administrators@BUILTIN"; "Remote Desktop Users@BUILTIN") -ComputerGroupType 2 |
|
Restart-Service TSGateway # We are done; Put everything into effect |
|
Stop-Transcript |
|
} |
|
GetScript = { |
|
Import-Module RemoteDesktopServices |
|
Return @{ Result = [string]$(Get-ChildItem -name RDS:\GatewayServer\SSLCertificate\Thumbprint) } |
|
} |
|
TestScript = { |
|
Import-Module RemoteDesktopServices |
|
If ((Get-ChildItem RDS:\GatewayServer\SSLCertificate\Thumbprint\).CurrentValue -eq "NULL") # There is no cert stored yet |
|
{ |
|
Return $false # The script has not run yet |
|
} |
|
Else |
|
{ |
|
Return $true # We have previously run this script |
|
} |
|
} |
|
} |
|
} |
|
} |
|
'@ |
|
|
|
#region VM administrative variables |
|
$AdminName = "administrator" # Administrator name in created VM |
|
$VmPassword = "AComplexPassword!@34!!??" # Admin password for VM |
|
$VmName = "RdgVm" # VMName -- also set to machine name |
|
#endregion |
|
|
|
#region Vnet parmeters |
|
$Loc = "eastus" # Azure region for all resources except automation account |
|
$Rg = "RdgVmRg" # Resource group name |
|
$AaLoc = "eastus2" # Azure region for automation account |
|
$VnetName = "Vnet" # VNet name |
|
$Cidr = "10.0.0.0/16" |
|
$PrivateIp = '10.0.20.5' # Private IP address to be assigned to the VM |
|
$FQDN = ($VmName + ".$Loc" + ".cloudapp.azure.com").ToLower() # Create the FQDN for use in the DSC configuration |
|
#endregion |
|
|
|
#region Storage account parameters |
|
$OsDiskName = $VmName + "OsDisk" |
|
$StorageAccount = ($VmName + "Storage").ToLower() |
|
$StorageAccountType = "Standard_LRS" # No need for expensive storage in this test script |
|
#endregion |
|
|
|
#region DSC-related parameters |
|
$Config = "InstallConfigureRemoteDesktopGateway" # Name of DSC configuration; must match name configuration name in here-string |
|
$nodeConfig = $Config + ".localhost" |
|
$ConfigFile = New-Item -Path $Env:TEMP\$Config.ps1 -ItemType File -Value $configText -Force # Create DSC configuration on local filesystem as Import-AzureRmAutomationDscConfiguration REQUIRES a file as input |
|
$ConfigSource = $ConfigFile.FullName |
|
$AaName = "DSCAutomationAccount" # Automation account name |
|
# DSC parameters MUST be in the form of a hashtable |
|
[hashtable]$DSCParms = @{"FQDN" = $FQDN;} |
|
#endregion |
|
|
|
Login-AzureRmAccount |
|
Select-AzureRMSubscription -SubscriptionName "AzureSubScriptionName" |
|
|
|
# Remove previous resource groups |
|
Remove-AzureRmResourceGroup -ResourceGroupName $Rg -Force |
|
|
|
# Create resource group |
|
New-AzureRmResourceGroup -Name $Rg -Location $Loc # Create containing resource group |
|
|
|
# Create first subnet configuration |
|
$SubnetConfig1 = New-AzureRmVirtualNetworkSubnetConfig -Name Subnet1 -AddressPrefix 10.0.10.0/24 |
|
|
|
# Create second subnet configuration |
|
$SubnetConfig2 = New-AzureRmVirtualNetworkSubnetConfig -Name Subnet2 -AddressPrefix 10.0.20.0/24 |
|
|
|
# Create a virtual network |
|
$Vnet = New-AzureRmVirtualNetwork -ResourceGroupName $Rg -Location $Loc ` |
|
-Name $VnetName -AddressPrefix $Cidr -Subnet $SubnetConfig1, $SubnetConfig2 |
|
|
|
# Create a public IP address and specify a DNS name |
|
$Pip = New-AzureRmPublicIpAddress -ResourceGroupName $Rg -Location $Loc ` |
|
-AllocationMethod Static -IdleTimeoutInMinutes 4 -Name "$Rg$(Get-Random)pip" -DomainNameLabel ($VmName).ToLower() # Sets DNS to $VmName.[region].cloudapp.azure.com |
|
|
|
# Create an inbound network security group rule for port 3389 |
|
$NsgRuleRdp = New-AzureRmNetworkSecurityRuleConfig -Name SecurityGroupRuleRDP -Protocol Tcp ` |
|
-Direction Inbound -Priority 1000 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * ` |
|
-DestinationPortRange 3389 -Access Allow |
|
|
|
# Create an inbound network security group rule for port 80 |
|
$NsgRuleWeb = New-AzureRmNetworkSecurityRuleConfig -Name SecurityGroupRuleWWW -Protocol Tcp ` |
|
-Direction Inbound -Priority 1001 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * ` |
|
-DestinationPortRange 80 -Access Allow |
|
|
|
# Create an inbound network security group rule for port 443 |
|
$NsgRuleTls = New-AzureRmNetworkSecurityRuleConfig -Name SecurityGroupRuleTLS -Protocol Tcp ` |
|
-Direction Inbound -Priority 1002 -SourceAddressPrefix * -SourcePortRange * -DestinationAddressPrefix * ` |
|
-DestinationPortRange 443 -Access Allow |
|
|
|
# Create a network security group |
|
$Nsg = New-AzureRmNetworkSecurityGroup -ResourceGroupName $Rg -Location $Loc ` |
|
-Name SecurityGroup -SecurityRules $NsgRuleRdp, $NsgRuleWeb, $NsgRuleTls |
|
|
|
# Create a virtual network card and associate with public IP address and NSG |
|
# In this script $Vnet.Subnets[0] = 10.0.10.0/24 and $Vnet.Subnets[1] = 10.0.20.0/24 |
|
# The value of $PrivateIp MUST match the containing subnet in the call to New-AzureRmNetworkInterface below |
|
$Nic = New-AzureRmNetworkInterface -Name Nic -ResourceGroupName $Rg -Location $Loc ` |
|
-SubnetId $Vnet.Subnets[1].Id -PublicIpAddressId $Pip.Id -NetworkSecurityGroupId $Nsg.Id -PrivateIpAddress $PrivateIp |
|
|
|
# For convenience, the password is hardcoded here and used to create $VmCred without prompting at the console |
|
$VmCred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $AdminName, ($VmPassword | ConvertTo-SecureString -AsPlainText -Force) |
|
|
|
# Create a storage account in this RG to hold the VHD |
|
$StgAcct = New-AzureRmStorageAccount -ResourceGroupName $Rg -Name $StorageAccount -Type $StorageAccountType -Location $Loc |
|
# Set up a pretty name for the VM's disk in the storage account |
|
$OSDiskUri = $StgAcct.PrimaryEndpoints.Blob.ToString() + "vhds/" + $OsDiskName + ".vhd" |
|
|
|
# Create PSVirtualMachine object for New-AzureRmVm |
|
$VmConfig = New-AzureRmVMConfig -VMName $VmName -VMSize Standard_D1_v2 | ` |
|
Set-AzureRmVMOperatingSystem -Windows -ComputerName $VmName -Credential $VmCred | ` |
|
Set-AzureRmVMSourceImage -PublisherName MicrosoftWindowsServer -Offer WindowsServer -Skus 2012-R2-Datacenter -Version latest | ` |
|
Add-AzureRmVMNetworkInterface -Id $Nic.Id | Set-AzureRmVMOSDisk -VhdUri $OSDiskUri -CreateOption FromImage |
|
|
|
# Create the virtual machine |
|
New-AzureRmVM -ResourceGroupName $Rg -Location $Loc -VM $VmConfig |
|
|
|
#Create an automation account in the new RG -- used for Auzre DSC |
|
New-AzureRmAutomationAccount -Name $AaName -Location $AaLoc -ResourceGroupName $Rg |
|
|
|
#Import the DSC Configuration |
|
Import-AzureRmAutomationDscConfiguration -AutomationAccountName $AaName -ResourceGroupName $Rg -Published -SourcePath $ConfigSource -Force |
|
#Remove the DSC configuration file from the local filesystem |
|
Remove-Item -Path $Env:TEMP\$Config.ps1 -Force |
|
|
|
#Compile the DSC Configuration |
|
Start-AzureRmAutomationDscCompilationJob -AutomationAccountName $AaName -ConfigurationName $Config -ResourceGroupName $Rg -Parameters $DSCParms |
|
|
|
# Register the new VM node with Azure DSC and apply the DSC configuration; be sure to specify the VMlocation |
|
Register-AzureRmAutomationDscNode -AzureVMName $VmName -ResourceGroupName $Rg -AutomationAccountName $AaName -NodeConfigurationName $nodeConfig -RebootNodeIfNeeded $true -AzureVMLocation $Loc -AllowModuleOverwrite $true -ConfigurationMode 'ApplyAndAutocorrect' |