1+ # Author: Aaron Roney.
2+ # Modified: 20170827.
3+ # Setup: Remove-Module Az.Compute; Import-Module .\Az.Compute.psm1; New-AzVm -Auto;
4+
5+ # Resources:
6+ # * [EXTERNAL] Azure PowerShell Docs: https://aka.ms/azpsdocs.
7+ # * [EXTERNAL] Strategy Doc: https://aka.ms/azpsstrategy.
8+ # * [EXTERNAL] Inspiration Sample: https://aka.ms/azpscreatevm.
9+ # * [EXTERNAL] Community Standups: https://aka.ms/azpsnetstandup.
10+ # * [EXTERNAL] Gallery Package: https://aka.ms/psazurerm.
11+ # * [EXTERNAL] Docker: https://aka.ms/azpsdocker.
12+ # * [EXTERNAL] Core Docker: https://aka.ms/azpscoredocker.
13+ # * [INTERNAL] Research Evidence: https://aka.ms/azpsimprovementresearch.
14+
15+ # TODO:
16+ # * Implement `-Auto' parameter set (i.e., `Name` and `ResourceGroup` will become optional).
17+ # * Implement new default formatter.
18+ # * Lock down parameter list: should be good as is (if we add the option to set OS disk size).
19+ # * Most of the parameters have "static" defaults: make these smart defaults, where applicable (e.g., location and open ports).
20+ # * WinServer2016 is hardcoded: add parameters to allow people to use the full image provider, etc.
21+ # * Import image providers, etc. from the "aliases.json".
22+ # * Integrate into Nelson's build semantics.
23+
24+ # Requires -Modules AzureRM .Compute
25+
26+ function New-AzVm {
27+ param (
28+ [Parameter (Mandatory = $true )] [string ] $Name ,
29+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
30+
31+ # Generate a random as a hash of the name so it will be idempotent (tack on resource group?).
32+ [Parameter (DontShow)]
33+ $Random = $ (Get-Hash $Name ),
34+
35+ [string ] $Location = " " ,
36+
37+ [string ] $Image = " WinServer2016" ,
38+ [string ] $Size = " Standard_DS1_v2" ,
39+
40+ [string ] $VnetName = " $ ( $Name ) Vnet" ,
41+ [string ] $SubnetAddressPrefix = " 192.168.1.0/24" ,
42+ [string ] $VnetAddressPrefix = " 192.168.0.0/16" ,
43+
44+ [string ] $PublicIpName = " $ ( $Name ) PublicIp" ,
45+ [string ] $PublicIpDnsLabel = " $Name -$Random " .ToLower(),
46+ [string ] $PublicIpAllocationMethod = " Static" ,
47+ [int ] $PublicIpIdleTimeoutInMinutes = 4 ,
48+
49+ [string ] $NsgName = " $ ( $Name ) Nsg" ,
50+ [int []] $NsgOpenPorts = $null ,
51+
52+ [string ] $NicName = " $ ( $Name ) Nic"
53+
54+ # Storage - OS Disk Size.
55+ # Compute: "this goes above and beyond the 80% scenario".
56+ )
57+
58+ try {
59+
60+ # Build credentials.
61+
62+ $userName = $env: UserName ;
63+ $ptPassword = -join ((33 .. 122 ) | Get-Random - Count 20 | ForEach-Object { [char ]$_ });
64+ $password = ConvertTo-SecureString $ptPassword - AsPlainText - Force;
65+ $creds = New-Object System.Management.Automation.PSCredential ($env: UserName , $password );
66+
67+ # Set the smart defaults.
68+
69+ if ($Location -eq " " ) {
70+ # TODO: Infer the location somehow?
71+ $Location = " westus2" ;
72+ }
73+
74+ if ($NsgOpenPorts -eq $null ) {
75+ # TODO: Infer the ports to open from the image types.
76+ $NsgOpenPorts = @ (3389 , 5985 );
77+ }
78+
79+ # Get image aliases.
80+
81+ # TODO: Properly set images from below. Put this in its own "ensure" method?
82+ $images = Invoke-WebRequest " https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/arm-compute/quickstart-templates/aliases.json" | ConvertFrom-Json ;
83+
84+ # Set variables.
85+
86+ $fqdn = " $PublicIpDnsLabel .$Location .cloudapp.azure.com" ;
87+
88+ # Create!
89+
90+ Write-Info " Ensuring resource group..." ;
91+ $rg = Use-ResourceGroup - ResourceGroup $ResourceGroup - Location $Location ;
92+
93+ Write-Info " Ensuring Vnet..." ;
94+ $vnet = Use-Vnet - Name $VnetName - ResourceGroup $ResourceGroup - Location $Location - SubnetAddressPrefix $SubnetAddressPrefix - VnetAddressPrefix $VnetAddressPrefix ;
95+
96+ Write-Info " Ensuring public IP..." ;
97+ $pip = Use-Pip - Name $PublicIpName - ResourceGroup $ResourceGroup - Location $Location - DnsLabel $PublicIpDnsLabel - AllocationMethod $PublicIpAllocationMethod - IdleTimeoutInMinutes $PublicIpIdleTimeoutInMinutes ;
98+
99+ Write-Info " Ensuring NSG..." ;
100+ $nsg = Use-Nsg - Name $PublicIpName - ResourceGroup $ResourceGroup - Location $Location - OpenPorts $NsgOpenPorts ;
101+ $Global :nsg = $nsg ;
102+
103+ Write-Info " Ensuring NIC..." ;
104+ $nic = Use-Nic - Name $NicName - ResourceGroup $ResourceGroup - Location $Location - SubnetId $vnet.Subnets [0 ].Id - PublicIpAddressId $pip.Id - NetworkSecurityGroupId $nsg.Id ;
105+
106+ # TODO: Add disk options (https://docs.microsoft.com/en-us/azure/virtual-machines/scripts/virtual-machines-windows-powershell-sample-create-vm-from-managed-os-disks?toc=%2fpowershell%2fmodule%2ftoc.json)?
107+ # https://docs.microsoft.com/en-us/powershell/module/azurerm.compute/set-azurermvmosdisk?view=azurermps-4.2.0
108+
109+ # Create a virtual machine configuration
110+ $vmConfig = New-AzureRmVMConfig - VMName $Name - VMSize $Size | `
111+ Set-AzureRmVMOperatingSystem - Windows - ComputerName $Name - Credential $creds | `
112+ Set-AzureRmVMSourceImage - PublisherName MicrosoftWindowsServer - Offer WindowsServer - Skus 2016 - Datacenter - Version latest | `
113+ Add-AzureRmVMNetworkInterface - Id $nic.Id
114+
115+ # Create a virtual machine
116+ $vm = New-AzureRmVM - ResourceGroupName $resourceGroup - Location $location - VM $vmConfig
117+
118+ # Write info about VM.
119+
120+ # TODO: Remove these and make this a formatter.
121+ # Write-Info "$($tab)Resource group: $rgName.";
122+ # Write-Info "$($tab)VM Name: MyVm$random.";
123+ # Write-Info "$($tab)Location: $location.";
124+ # Write-Info "$($tab)FQDN: $fqdn.";
125+ Write-Info " $ ( $tab ) Username: $username ." ;
126+ Write-Info " $ ( $tab ) Password: $ptPassword ." ;
127+
128+ return $vm ;
129+ } catch {
130+ Write-Error $_ ;
131+ Write-Error " Something went wrong. Issue the command again: it is idempotent. :)" ;
132+ }
133+ }
134+
135+ Export-ModuleMember - Function New-AzVm
136+
137+ # Helpers.
138+
139+ function Use-ResourceGroup {
140+ param (
141+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
142+ [Parameter (Mandatory = $true )] [string ] $Location
143+ )
144+
145+ $rg = Get-AzureRmResourceGroup | Where-Object { $_.ResourceGroupName -eq $ResourceGroup } | Select-Object - First 1 - Wait;
146+
147+ if ($rg -eq $null ) {
148+ return New-AzureRmResourceGroup - Name $ResourceGroup - Location $Location ;
149+ } else {
150+ return $rg ;
151+ }
152+ }
153+
154+ function Use-Vnet {
155+ param (
156+ [Parameter (Mandatory = $true )] [string ] $Name ,
157+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
158+ [Parameter (Mandatory = $true )] [string ] $Location ,
159+ [Parameter (Mandatory = $true )] [string ] $SubnetAddressPrefix ,
160+ [Parameter (Mandatory = $true )] [string ] $VnetAddressPrefix
161+ )
162+
163+ $vnet = Get-AzureRmVirtualNetwork | Where-Object { $_.Name -eq $Name } | Select-Object - First 1 - Wait;
164+
165+ if ($vnet -eq $null ) {
166+ # Create a subnet configuration.
167+ $subnetConfig = New-AzureRmVirtualNetworkSubnetConfig - Name " $ ( $Name ) Subnet" - AddressPrefix $SubnetAddressPrefix ;
168+
169+ # Create a virtual network.
170+ return New-AzureRmVirtualNetwork - ResourceGroupName $ResourceGroup - Location $Location - Name $Name - AddressPrefix $VnetAddressPrefix - Subnet $subnetConfig
171+ } else {
172+ return $vnet ;
173+ }
174+ }
175+
176+ function Use-Pip {
177+ param (
178+ [Parameter (Mandatory = $true )] [string ] $Name ,
179+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
180+ [Parameter (Mandatory = $true )] [string ] $Location ,
181+ [Parameter (Mandatory = $true )] [string ] $DnsLabel ,
182+ [Parameter (Mandatory = $true )] [string ] $AllocationMethod ,
183+ [Parameter (Mandatory = $true )] [int ] $IdleTimeoutInMinutes
184+ )
185+
186+ $pip = Get-AzureRmPublicIpAddress | Where-Object { $_.Name -eq $Name } | Select-Object - First 1 - Wait;
187+
188+ if ($pip -eq $null ) {
189+ # Create a public IP address and specify a DNS name.
190+ return New-AzureRmPublicIpAddress - ResourceGroupName $ResourceGroup - Location $Location - Name $Name - DomainNameLabel $DnsLabel - AllocationMethod $AllocationMethod - IdleTimeoutInMinutes $IdleTimeoutInMinutes ;
191+ } else {
192+ return $pip ;
193+ }
194+ }
195+
196+ function Use-Nsg {
197+ param (
198+ [Parameter (Mandatory = $true )] [string ] $Name ,
199+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
200+ [Parameter (Mandatory = $true )] [string ] $Location ,
201+ [Parameter (Mandatory = $true )] [int []] $OpenPorts
202+ )
203+
204+ $nsg = Get-AzureRmNetworkSecurityGroup | Where-Object { $_.Name -eq $Name } | Select-Object - First 1 - Wait;
205+
206+ if ($nsg -eq $null ) {
207+ $rules = New-Object " System.Collections.Generic.List[Microsoft.Azure.Commands.Network.Models.PSSecurityRule]" ;
208+ $priority = 1000 ;
209+
210+ foreach ($port in $OpenPorts )
211+ {
212+ $nsgRule = New-AzureRmNetworkSecurityRuleConfig - Name myNetworkSecurityGroupRuleRDP - Protocol Tcp - Direction Inbound - Priority $priority - SourceAddressPrefix * - SourcePortRange * - DestinationAddressPrefix * - DestinationPortRange $port - Access Allow;
213+ $rules.Add ($nsgRule );
214+
215+ $priority -- ;
216+ }
217+
218+ # Create an NSG.
219+ return New-AzureRmNetworkSecurityGroup - ResourceGroupName $ResourceGroup - Location $Location - Name $Name - SecurityRules $rules ;
220+ } else {
221+ return $nsg ;
222+ }
223+ }
224+
225+ function Use-Nic {
226+ param (
227+ [Parameter (Mandatory = $true )] [string ] $Name ,
228+ [Parameter (Mandatory = $true )] [string ] $ResourceGroup ,
229+ [Parameter (Mandatory = $true )] [string ] $Location ,
230+ [Parameter (Mandatory = $true )] [string ] $SubnetId ,
231+ [Parameter (Mandatory = $true )] [string ] $PublicIpAddressId ,
232+ [Parameter (Mandatory = $true )] [psobject ] $NetworkSecurityGroupId
233+ )
234+
235+ $nic = Get-AzureRmNetworkInterface | Where-Object { $_.Name -eq $Name } | Select-Object - First 1 - Wait;
236+
237+ if ($nic -eq $null ) {
238+ # Create a virtual network card and associate with public IP address and NSG
239+ return New-AzureRmNetworkInterface - Name $Name - ResourceGroupName $resourceGroup - Location $location - SubnetId $SubnetId - PublicIpAddressId $PublicIpAddressId - NetworkSecurityGroupId $NetworkSecurityGroupId.ToString ();
240+ } else {
241+ return $nic ;
242+ }
243+ }
244+
245+ function Write-Info {
246+ param (
247+ [Parameter (Position = 0 , Mandatory = $true , ValueFromPipeline = $true )] [String ] $Text
248+ )
249+
250+ Write-Host $Text - ForegroundColor Cyan;
251+ }
252+
253+ function Get-Hash {
254+ param (
255+ [Parameter (Position = 0 , Mandatory = $true , ValueFromPipeline = $true )] [String ] $TextToHash ,
256+ [int ] $Count = 10
257+ )
258+
259+ $hasher = new-object System.Security.Cryptography.SHA256Managed;
260+ $toHash = [System.Text.Encoding ]::UTF8.GetBytes($TextToHash );
261+ $hashByteArray = $hasher.ComputeHash ($toHash );
262+
263+ foreach ($byte in $hashByteArray )
264+ {
265+ $res += $byte.ToString ();
266+ }
267+
268+ return $res.substring (0 , $Count );
269+ }
0 commit comments