Last active
April 29, 2024 05:39
-
-
Save rkttu/1da8ff831c205eb6850ce41dc0e1dad5 to your computer and use it in GitHub Desktop.
Create Azure Windows VM, Connect WinRM, RDP with LINQPad (Demo)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
After running as administrator | |
- Entering the "winrm quickconfig" command if this is your first time setting up winrm | |
- If you get an error message that says it failed because your network profile is set to public, that's okay. | |
- To allow all external connections, run "winrm s winrm/config/client @{TrustedHosts="*"}" command | |
- After you're done testing, run the "winrm s winrm/config/client @{TrustedHosts=""}" command | |
*/ | |
var credential = default(TokenCredential); | |
#if LINQPAD | |
credential = new InteractiveBrowserCredential(); | |
#else | |
credential = new DefaultAzureCredential(); | |
#endif | |
var location = AzureLocation.KoreaCentral; | |
var prefix = "krazure"; | |
var adminUsername = "azureuser"; | |
var adminPassword = CloudBuilderAzureExtensions.CreateRandomPassword(); | |
var customDataContent = @" | |
(Custom Data) Hello, World! | |
"; | |
var userDataContent = @" | |
(User Data) Hello, World! | |
"; | |
var cancellationToken = default(CancellationToken); | |
var modificationPowerShellCommands = new string[] { | |
"dir C:\\", | |
}; | |
var client = new ArmClient(credential); | |
await Console.Out.WriteLineAsync( | |
"Looking up subscription...".AsMemory(), cancellationToken) | |
.ConfigureAwait(false); | |
var subscription = await client.GetDefaultSubscriptionAsync( | |
cancellationToken).ConfigureAwait(false); | |
ResourceGroupResource? resourceGroup = default; | |
StorageAccountResource? logStorageAccount = default; | |
VirtualNetworkResource? virtualNetwork = default; | |
NetworkSecurityGroupResource? networkSecurityGroup = default; | |
PublicIPAddressResource? publicIP = default; | |
NetworkInterfaceResource? networkInterface = default; | |
VirtualMachineResource? virtualMachine = default; | |
SecureString? adminPasswordSecureString = default; | |
string? rdpFilePath = default; | |
ManagedDiskResource? osDisk = default; | |
try | |
{ | |
await Console.Out.WriteLineAsync("Creating Resource Group...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
resourceGroup = await subscription.CreateResourceGroupAsync(prefix, location, cancellationToken).ConfigureAwait(false); | |
var publicIPTask = resourceGroup.CreatePublicIPAddressAsync(prefix, cancellationToken); | |
var virtualNetworkTask = resourceGroup.CreateVirtualNetworkAsync(prefix, cancellationToken); | |
var securityGroupTask = resourceGroup.CreateNetworkSecurityGroupAsync(prefix, cancellationToken); | |
var logStorageAccountTask = resourceGroup.CreateStorageAccountAsync(prefix, cancellationToken); | |
await Task.WhenAll( | |
Console.Out.WriteLineAsync( | |
"Creating Public IP, Virtual Network, Security Group, and Log Storage Account...".AsMemory(), | |
cancellationToken), | |
publicIPTask, | |
virtualNetworkTask, | |
securityGroupTask, | |
logStorageAccountTask).ConfigureAwait(false); | |
publicIP = await publicIPTask.ConfigureAwait(false); | |
virtualNetwork = await virtualNetworkTask.ConfigureAwait(false); | |
networkSecurityGroup = await securityGroupTask.ConfigureAwait(false); | |
logStorageAccount = await logStorageAccountTask.ConfigureAwait(false); | |
var subnet = virtualNetwork.GetSubnets().First(); | |
await Console.Out.WriteLineAsync("Creating Network Interface...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
networkInterface = await resourceGroup.CreateNetworkInterfaceAsync( | |
prefix, publicIP.Data, subnet.Data, networkSecurityGroup.Data, cancellationToken).ConfigureAwait(false); | |
var targetEncoding = new UTF8Encoding(false); | |
await Console.Out.WriteLineAsync("Creating Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
virtualMachine = await resourceGroup.CreateVirtualMachineAsync( | |
prefix, | |
"computername", | |
adminUsername, | |
adminPassword, | |
networkInterface.Data, | |
customData: targetEncoding.GetBytes(customDataContent), | |
userData: targetEncoding.GetBytes(userDataContent), | |
logStorageAccountUri: logStorageAccount.Data.PrimaryEndpoints.BlobUri, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync("Turning on Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await virtualMachine.PowerOnAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync("Fetching Public IP Address...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
var ipAddrData = await resourceGroup.FetchPublicIPAddressData(publicIP.Data.Name, cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync($"VM Address: {ipAddrData.IPAddress}".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync($"VM Admin User Name: {adminUsername}".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync($"VM Admin Password: {adminPassword}".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync($"Try connecting to the VM with WinRM...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
unsafe | |
{ | |
fixed (char* pAdminPassword = adminPassword) | |
{ | |
adminPasswordSecureString = new SecureString(pAdminPassword, adminPassword.Length); | |
} | |
} | |
Runspace? runspace = null; | |
for (int i = 0, max = 10; i < max; i++) | |
{ | |
try | |
{ | |
runspace = RunspaceFactory.CreateRunspace(new WSManConnectionInfo() | |
{ | |
ComputerName = ipAddrData.IPAddress, | |
Credential = new PSCredential(adminUsername, adminPasswordSecureString), | |
OpenTimeout = (int)TimeSpan.FromSeconds(30d).TotalMilliseconds, | |
}); | |
runspace.Open(); | |
} | |
catch (Exception e) | |
{ | |
if (runspace != null) | |
{ | |
runspace.Dispose(); | |
runspace = null; | |
} | |
await Console.Error.WriteLineAsync($"Error Found: {e.Message} - Retrying {i + 1}/{max}...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await Task.Delay(TimeSpan.FromSeconds(1d), cancellationToken).ConfigureAwait(false); | |
} | |
} | |
if (runspace == null) | |
throw new Exception("Cannot connect to the remote VM with PowerShell Remoting."); | |
await Console.Out.WriteLineAsync($"Connected VM with WinRM channel.".AsMemory(), cancellationToken).ConfigureAwait(false); | |
using (var ps = PowerShell.Create(runspace)) | |
{ | |
ps.Streams.Progress.DataAdded += (_sender, _e) => | |
{ | |
var records = (PSDataCollection<ProgressRecord>)_sender!; | |
var current = records.ElementAtOrDefault(_e.Index); | |
if (current == null) | |
return; | |
if (current.RecordType == ProgressRecordType.Processing) | |
Console.Out.Write($"\r{current.Activity}: {current.PercentComplete}%"); | |
else if (current.RecordType == ProgressRecordType.Completed) | |
Console.Out.Write($"\r{current.Activity}: Completed\n"); | |
}; | |
PSDataCollection<PSObject>? res; | |
foreach (var eachCommand in modificationPowerShellCommands) | |
{ | |
try | |
{ | |
res = await ps.RunScriptAsync(eachCommand ?? string.Empty).ConfigureAwait(false); | |
res.Dump(eachCommand); | |
} | |
catch (Exception ex) { ex.Dump(eachCommand); } | |
} | |
rdpFilePath = await PowerShellHelpers.GenerateRdpFileWithPasswordEmbedding( | |
virtualMachine.Data.Name, | |
ipAddrData.IPAddress, | |
adminUsername, | |
adminPassword).ConfigureAwait(false); | |
rdpFilePath.Dump("RDP File Demo"); | |
new Button("Click Here to Open Remote Desktop Session", | |
_ => | |
{ | |
Process.Start( | |
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "mstsc.exe"), | |
rdpFilePath); | |
}) | |
.Dump(); | |
const string exitCommand = "<exit>"; | |
await Console.Out.WriteLineAsync($"Type {exitCommand} to stop VM...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
while (true) | |
{ | |
var eachCommand = await Console.In.ReadLineAsync(cancellationToken); | |
if (string.Equals(eachCommand?.Trim()?.ToLowerInvariant(), exitCommand)) | |
break; | |
try | |
{ | |
res = await ps.RunScriptAsync(eachCommand ?? string.Empty).ConfigureAwait(false); | |
res.Dump(eachCommand); | |
} | |
catch (Exception ex) { ex.Dump(eachCommand); } | |
} | |
// sysprep이 작업을 마치고나면 시스템 SID와 인증서가 모두 교체되기 때문에 더 이상 원격 연결이 유지되지 않음. | |
// 이후에는 클라우드 공급자 측의 API를 사용하여 인스턴스를 종료해야 함. | |
await Console.Out.WriteLineAsync($"Trying system preparation procedure...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await ps.RunScriptAsync(@"Start-Process -FilePath ""$env:WINDIR\System32\Sysprep\sysprep.exe"" -ArgumentList '/oobe /quit /quiet /generalize' -Wait -NoNewWindow").ConfigureAwait(false); | |
var osDiskName = virtualMachine.Data.StorageProfile.OSDisk.Name; | |
await Console.Out.WriteLineAsync("Finalizing: Turning off the Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await virtualMachine.PowerOffAsync(WaitUntil.Completed, false, cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync("Finalizing: Removing Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteVirtualMachineAsync(virtualMachine.Data.Name, cancellationToken).ConfigureAwait(false); | |
networkInterface = null; | |
virtualMachine = null; | |
await Console.Out.WriteLineAsync("Finalizing: Granting VHD SAS URI for 1 day...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
osDisk = await resourceGroup.GetManagedDisks().GetAsync(osDiskName, cancellationToken).ConfigureAwait(false); | |
var accessOperation = await osDisk.GrantAccessAsync( | |
WaitUntil.Completed, | |
new GrantAccessData(AccessLevel.Read, (int)TimeSpan.FromDays(1d).TotalSeconds), | |
cancellationToken).ConfigureAwait(false); | |
var access = await accessOperation.WaitForCompletionAsync(cancellationToken).ConfigureAwait(false); | |
var url = access.Value.AccessSas; | |
await Console.Out.WriteLineAsync($"You can download VHD from here: {url}".AsMemory(), cancellationToken).ConfigureAwait(false); | |
new LINQPad.Controls.Hyperlink("Download VHD", url).Dump(); | |
var continueTcs = new TaskCompletionSource(); | |
new Button("Terminate Resources", (button) => | |
{ | |
continueTcs.SetResult(); | |
}).Dump(); | |
await continueTcs.Task.ConfigureAwait(false); | |
} | |
} | |
catch (Exception ex) | |
{ | |
await Console.Error.WriteLineAsync( | |
ex.ToString().AsMemory(), | |
cancellationToken).ConfigureAwait(false); | |
} | |
finally | |
{ | |
if (resourceGroup != null) | |
{ | |
if (virtualMachine != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Turning off the Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await virtualMachine.PowerOffAsync(WaitUntil.Completed, false, cancellationToken).ConfigureAwait(false); | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Virtual Machine...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteVirtualMachineAsync(virtualMachine.Data.Name, cancellationToken).ConfigureAwait(false); | |
// Skip Network Interface Delete Operation | |
networkInterface = null; | |
} | |
if (osDisk != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing OS Disk...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
try { await osDisk.RevokeAccessAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); } | |
catch { } | |
await osDisk.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
if (networkInterface != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Network Interface...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteNetworkInterface(networkInterface.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
if (publicIP != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Public IP...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeletePublicIPAddressAsync(publicIP.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
if (networkSecurityGroup != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Network Security Group...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteNetworkSecurityGroupAsync(networkSecurityGroup.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
if (virtualNetwork != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Virtual Network...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteVirtualNetworkAsync(virtualNetwork.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
if (logStorageAccount != null) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Log Storage Account...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await resourceGroup.DeleteStorageAccountAsync(logStorageAccount.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
if (rdpFilePath != null && File.Exists(rdpFilePath)) | |
{ | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing RDP File...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
try { File.Delete(rdpFilePath); } | |
catch { } | |
} | |
await Console.Out.WriteLineAsync("Cleaning Up: Removing Resource Group...".AsMemory(), cancellationToken).ConfigureAwait(false); | |
await subscription.DeleteResourceGroupAsync(resourceGroup.Data.Name, cancellationToken).ConfigureAwait(false); | |
} | |
} | |
public static class CloudBuilderAzureExtensions | |
{ | |
public static async Task<ResourceGroupResource> CreateResourceGroupAsync( | |
this SubscriptionResource subscription, | |
string prefix, | |
AzureLocation azureLocation, | |
CancellationToken cancellationToken = default) | |
{ | |
var resourceGroups = subscription.GetResourceGroups(); | |
var newResGroupName = $"{prefix}_{Guid.NewGuid().ToString("n")}"; | |
await resourceGroups.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newResGroupName, | |
new ResourceGroupData(azureLocation), | |
cancellationToken).ConfigureAwait(false); | |
var createdResourceGroup = await resourceGroups.GetAsync( | |
newResGroupName, | |
cancellationToken).ConfigureAwait(false); | |
return createdResourceGroup.Value; | |
} | |
private static readonly string _ForceDeletionTypes = string.Join(',', new string[] | |
{ | |
"Microsoft.Compute/virtualMachines", | |
"Microsoft.Compute/virtualMachineScaleSets", | |
}); | |
public static async Task DeleteResourceGroupAsync( | |
this SubscriptionResource subscription, | |
string resourceGroupName, | |
CancellationToken cancellationToken = default) | |
{ | |
var resourceGroups = subscription.GetResourceGroups(); | |
var targetResGroup = (ResourceGroupResource)await resourceGroups.GetAsync(resourceGroupName, cancellationToken).ConfigureAwait(false); | |
await targetResGroup.DeleteAsync(WaitUntil.Completed, _ForceDeletionTypes, cancellationToken).ConfigureAwait(false); | |
} | |
public static async Task<StorageAccountResource> CreateStorageAccountAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
CancellationToken cancellationToken = default) | |
{ | |
var storageAccounts = resourceGroup.GetStorageAccounts(); | |
// Storage name should contains only numbers and lower case letter only (min length: 3, max length: 24) | |
var newStorageAccountName = $"{prefix}stor{Guid.NewGuid().ToString("n")}".Substring(0, 24); | |
var newStorageAccountData = new StorageAccountCreateOrUpdateContent( | |
new StorageSku(StorageSkuName.StandardLrs), | |
StorageKind.StorageV2, | |
resourceGroup.Data.Location); | |
await storageAccounts.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newStorageAccountName, | |
newStorageAccountData, | |
cancellationToken).ConfigureAwait(false); | |
var createdStorageAccount = await storageAccounts.GetAsync( | |
newStorageAccountName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdStorageAccount.Value; | |
} | |
public static async Task DeleteStorageAccountAsync( | |
this ResourceGroupResource resourceGroup, | |
string storageAccountName, | |
CancellationToken cancellationToken = default) | |
{ | |
var storageAccounts = resourceGroup.GetStorageAccounts(); | |
var targetStorageAccount = await storageAccounts.GetAsync(storageAccountName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetStorageAccount.Value.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
public static async Task<VirtualNetworkResource> CreateVirtualNetworkAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
CancellationToken cancellationToken = default) | |
{ | |
var virtualNetworks = resourceGroup.GetVirtualNetworks(); | |
var newVnetName = $"{prefix}_vnet_{Guid.NewGuid().ToString("n")}"; | |
var newVnetData = new VirtualNetworkData() | |
{ | |
EnableDdosProtection = false, | |
EnableVmProtection = false, | |
Location = resourceGroup.Data.Location, | |
}; | |
newVnetData.AddressPrefixes.Add("10.0.0.0/16"); | |
newVnetData.Subnets.Add(new SubnetData() { Name = $"{prefix}_subnet_{Guid.NewGuid().ToString("n")}", AddressPrefix = "10.0.0.0/24", }); | |
await virtualNetworks.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newVnetName, | |
newVnetData, | |
cancellationToken).ConfigureAwait(false); | |
var createdVnet = await virtualNetworks.GetAsync( | |
newVnetName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdVnet.Value; | |
} | |
public static async Task DeleteVirtualNetworkAsync( | |
this ResourceGroupResource resourceGroup, | |
string virtualNetworkName, | |
CancellationToken cancellationToken = default) | |
{ | |
var virtualNetworks = resourceGroup.GetVirtualNetworks(); | |
var targetVnet = await virtualNetworks.GetAsync(virtualNetworkName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetVnet.Value.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
public static async Task<NetworkSecurityGroupResource> CreateNetworkSecurityGroupAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
CancellationToken cancellationToken = default) | |
{ | |
var networkSecurityGroups = resourceGroup.GetNetworkSecurityGroups(); | |
var newNetworkSecurityGroupName = $"{prefix}_nsg_{Guid.NewGuid().ToString("n")}"; | |
var newNetworkSecurityGroupData = new NetworkSecurityGroupData() | |
{ | |
Location = resourceGroup.Data.Location, | |
}; | |
var priority = 100; | |
newNetworkSecurityGroupData.SecurityRules.Add(new SecurityRuleData() | |
{ | |
Priority = priority++, | |
Name = "inbound_tcp_allow_winrm", | |
Description = "(Inbound/TCP) Allow WinRM (HTTP/HTTPS)", | |
Access = SecurityRuleAccess.Allow, | |
Direction = SecurityRuleDirection.Inbound, | |
Protocol = SecurityRuleProtocol.Tcp, | |
SourceAddressPrefix = "*", | |
SourcePortRange = "*", | |
DestinationAddressPrefix = "*", | |
DestinationPortRange = "5985-5986", | |
}); | |
newNetworkSecurityGroupData.SecurityRules.Add(new SecurityRuleData() | |
{ | |
Priority = priority++, | |
Name = "inbound_tcp_allow_rdp", | |
Description = "(Inbound/TCP) Allow Remote Desktop", | |
Access = SecurityRuleAccess.Allow, | |
Direction = SecurityRuleDirection.Inbound, | |
Protocol = SecurityRuleProtocol.Tcp, | |
SourceAddressPrefix = "*", | |
SourcePortRange = "*", | |
DestinationAddressPrefix = "*", | |
DestinationPortRange = "3389", | |
}); | |
await networkSecurityGroups.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newNetworkSecurityGroupName, | |
newNetworkSecurityGroupData, | |
cancellationToken).ConfigureAwait(false); | |
var createdNsg = await networkSecurityGroups.GetAsync( | |
newNetworkSecurityGroupName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdNsg.Value; | |
} | |
public static async Task DeleteNetworkSecurityGroupAsync( | |
this ResourceGroupResource resourceGroup, | |
string networkSecurityGroupName, | |
CancellationToken cancellationToken = default) | |
{ | |
var networkSecurityGroups = resourceGroup.GetNetworkSecurityGroups(); | |
var targetNSG = await networkSecurityGroups.GetAsync(networkSecurityGroupName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetNSG.Value.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
public static async Task<PublicIPAddressResource> CreatePublicIPAddressAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
CancellationToken cancellationToken = default) | |
{ | |
var publicIPAddresses = resourceGroup.GetPublicIPAddresses(); | |
var newPublicIPAddressName = $"{prefix}_publicip_{Guid.NewGuid().ToString("n")}"; | |
var newPublicIPAddressData = new PublicIPAddressData() | |
{ | |
PublicIPAllocationMethod = NetworkIPAllocationMethod.Dynamic, | |
Location = resourceGroup.Data.Location, | |
}; | |
await publicIPAddresses.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newPublicIPAddressName, | |
newPublicIPAddressData, | |
cancellationToken).ConfigureAwait(false); | |
var createdPublicIPAddress = await publicIPAddresses.GetAsync( | |
newPublicIPAddressName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdPublicIPAddress.Value; | |
} | |
public static async Task DeletePublicIPAddressAsync( | |
this ResourceGroupResource resourceGroup, | |
string publicIPAddressName, | |
CancellationToken cancellationToken = default) | |
{ | |
var publicIPAddresses = resourceGroup.GetPublicIPAddresses(); | |
var targetPublicIPAddress = await publicIPAddresses.GetAsync(publicIPAddressName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetPublicIPAddress.Value.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
public static async Task<PublicIPAddressData> FetchPublicIPAddressData( | |
this ResourceGroupResource resourceGroup, | |
string publicIPAddressName, | |
CancellationToken cancellationToken = default) | |
{ | |
var publicIPAddresses = resourceGroup.GetPublicIPAddresses(); | |
while (true) | |
{ | |
var targetPublicIPAddress = await publicIPAddresses.GetAsync(publicIPAddressName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
if (string.IsNullOrWhiteSpace(targetPublicIPAddress.Value.Data.IPAddress)) | |
{ | |
await Task.Delay(TimeSpan.FromSeconds(1d), cancellationToken).ConfigureAwait(false); | |
continue; | |
} | |
return targetPublicIPAddress.Value.Data; | |
} | |
} | |
public static async Task<NetworkInterfaceResource> CreateNetworkInterfaceAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
PublicIPAddressData publicIPData, | |
SubnetData subnetData, | |
NetworkSecurityGroupData networkSecurityGroupData, | |
CancellationToken cancellationToken = default) | |
{ | |
var networkInterfaces = resourceGroup.GetNetworkInterfaces(); | |
var newNetworkInterfaceName = $"{prefix}_nic_{Guid.NewGuid().ToString("n")}"; | |
var newNetworkInterfaceData = new NetworkInterfaceData() | |
{ | |
NicType = NetworkInterfaceNicType.Standard, | |
EnableAcceleratedNetworking = true, | |
Location = resourceGroup.Data.Location, | |
NetworkSecurityGroup = networkSecurityGroupData, | |
}; | |
newNetworkInterfaceData.IPConfigurations.Add(new NetworkInterfaceIPConfigurationData() | |
{ | |
Name = $"{prefix}_ipconf_{Guid.NewGuid().ToString("n")}", | |
PublicIPAddress = publicIPData, | |
Subnet = subnetData, | |
}); | |
await networkInterfaces.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newNetworkInterfaceName, | |
newNetworkInterfaceData, | |
cancellationToken).ConfigureAwait(false); | |
var createdNetworkInterface = await networkInterfaces.GetAsync( | |
newNetworkInterfaceName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdNetworkInterface.Value; | |
} | |
public static async Task DeleteNetworkInterface( | |
this ResourceGroupResource resourceGroup, | |
string networkInterfaceName, | |
CancellationToken cancellationToken = default) | |
{ | |
var networkInterfaces = resourceGroup.GetNetworkInterfaces(); | |
var targetNetworkInterface = await networkInterfaces.GetAsync(networkInterfaceName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetNetworkInterface.Value.DeleteAsync(WaitUntil.Completed, cancellationToken).ConfigureAwait(false); | |
} | |
private static readonly char[] _CharSet = | |
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^&*()_+{}" | |
.ToCharArray(); | |
public static string CreateRandomPassword(int length = 12) | |
{ | |
using (var rng = RandomNumberGenerator.Create()) | |
{ | |
var bytes = new byte[length = Math.Min(32, Math.Max(12, length))]; | |
rng.GetBytes(bytes, 0, bytes.Length); | |
var result = new char[length]; | |
for (var i = 0; i < length; i++) | |
result[i] = _CharSet[bytes[i] % _CharSet.Length]; | |
return new string(result); | |
} | |
} | |
public static async Task<VirtualMachineResource> CreateVirtualMachineAsync( | |
this ResourceGroupResource resourceGroup, | |
string prefix, | |
string computerName, | |
string adminUserName, | |
string adminPassword, | |
NetworkInterfaceData networkInterfaceData, | |
Uri? logStorageAccountUri = default, | |
byte[]? customData = default, | |
byte[]? userData = default, | |
CancellationToken cancellationToken = default) | |
{ | |
var virtualMachines = resourceGroup.GetVirtualMachines(); | |
var newVirtualMachineName = $"{prefix}_vm_{Guid.NewGuid().ToString("n")}"; | |
var newWindowsVirtualMachineConfiguration = new WindowsConfiguration() | |
{ | |
ProvisionVmAgent = true, | |
EnableAutomaticUpdates = true, | |
TimeZone = "Korea Standard Time", | |
}; | |
newWindowsVirtualMachineConfiguration.WinRMListeners.Add(new WinRMListener() | |
{ | |
Protocol = WinRMListenerProtocolType.Http, | |
}); | |
var newVirtualMachineData = new VirtualMachineData(resourceGroup.Data.Location) | |
{ | |
HardwareProfile = new VirtualMachineHardwareProfile() | |
{ | |
VmSize = VirtualMachineSizeType.StandardD2V3, | |
}, | |
StorageProfile = new VirtualMachineStorageProfile() | |
{ | |
ImageReference = new ImageReference() | |
{ | |
Publisher = "MicrosoftWindowsServer", | |
Offer = "WindowsServer", | |
Sku = "2022-Datacenter", | |
Version = "latest" | |
}, | |
OSDisk = new VirtualMachineOSDisk(DiskCreateOptionType.FromImage) | |
{ | |
DeleteOption = DiskDeleteOptionType.Detach, | |
OSType = SupportedOperatingSystemType.Windows, | |
}, | |
}, | |
OSProfile = new VirtualMachineOSProfile() | |
{ | |
ComputerName = computerName, | |
AdminUsername = adminUserName, | |
AdminPassword = adminPassword, | |
WindowsConfiguration = newWindowsVirtualMachineConfiguration, | |
}, | |
NetworkProfile = new VirtualMachineNetworkProfile() | |
{ | |
NetworkInterfaces = | |
{ | |
new VirtualMachineNetworkInterfaceReference() | |
{ | |
Id = networkInterfaceData.Id, | |
Primary = true, | |
DeleteOption = ComputeDeleteOption.Delete, | |
} | |
} | |
}, | |
BootDiagnostics = new BootDiagnostics() | |
{ | |
Enabled = (logStorageAccountUri != null), | |
StorageUri = logStorageAccountUri, | |
}, | |
}; | |
if (customData != null && customData.Length > 0) | |
newVirtualMachineData.OSProfile.CustomData = Convert.ToBase64String(customData, 0, customData.Length); | |
if (userData != null && userData.Length > 0) | |
newVirtualMachineData.UserData = Convert.ToBase64String(userData, 0, userData.Length); | |
await virtualMachines.CreateOrUpdateAsync( | |
WaitUntil.Completed, | |
newVirtualMachineName, | |
newVirtualMachineData, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
var createdVirtualMachine = await virtualMachines.GetAsync( | |
newVirtualMachineName, | |
cancellationToken: cancellationToken).ConfigureAwait(false); | |
return createdVirtualMachine.Value; | |
} | |
public static async Task DeleteVirtualMachineAsync( | |
this ResourceGroupResource resourceGroup, | |
string virtualMachineName, | |
CancellationToken cancellationToken = default) | |
{ | |
var virtualMachines = resourceGroup.GetVirtualMachines(); | |
var targetVirtualMachine = await virtualMachines.GetAsync(virtualMachineName, cancellationToken: cancellationToken).ConfigureAwait(false); | |
await targetVirtualMachine.Value.DeleteAsync(WaitUntil.Completed, true, cancellationToken).ConfigureAwait(false); | |
} | |
} | |
public static class PowerShellHelpers | |
{ | |
public static async Task<PSDataCollection<PSObject>> RunScriptAsync( | |
this PowerShell ps, string scriptContents) | |
{ | |
if (ps == null) | |
throw new ArgumentNullException(nameof(ps)); | |
ps.Streams.ClearStreams(); | |
ps.Commands.Clear(); | |
ps.AddScript(scriptContents); | |
var result = await Task.Factory.FromAsync(ps.BeginInvoke(), ps.EndInvoke).ConfigureAwait(false); | |
if (ps.Streams.Error.Count > 0) | |
throw new PowerShellScriptFailureException(ps.Streams.Error.ToArray()); | |
return result; | |
} | |
// Original Source Code: https://github.com/RedAndBlueEraser/rdp-file-password-encryptor | |
public static async Task<string> GenerateRdpFileWithPasswordEmbedding( | |
string resourceName, string serverAddress, string userAccountName, string rawPassword, | |
CancellationToken cancellationToken = default) | |
{ | |
var optionalEntropy = default(byte[]); | |
var protectionScope = DataProtectionScope.CurrentUser; | |
var encrypted = string.Concat(ProtectedData.Protect( | |
Encoding.Unicode.GetBytes(rawPassword), | |
optionalEntropy, protectionScope).Select(x => $"{x:X2}")); | |
var buffer = new StringBuilder(); | |
buffer.AppendLine(string.Concat("full address:s:", serverAddress)); | |
buffer.AppendLine(string.Concat("username:s:", userAccountName)); | |
buffer.AppendLine(string.Concat("password 51:b:", encrypted)); | |
var rdpFilePath = Path.Combine( | |
Path.GetTempPath(), | |
$"{resourceName}.rdp"); | |
await File.WriteAllTextAsync( | |
rdpFilePath, buffer.ToString(), | |
Encoding.ASCII, cancellationToken) | |
.ConfigureAwait(false); | |
return rdpFilePath; | |
} | |
} | |
public sealed class PowerShellScriptFailureException : Exception | |
{ | |
public PowerShellScriptFailureException(ErrorRecord[] errorRecords) | |
{ | |
_errorRecords = errorRecords; | |
} | |
private readonly ErrorRecord[] _errorRecords; | |
public override string Message | |
=> this.ToString(); | |
public IReadOnlyList<ErrorRecord> ErrorRecords() | |
=> _errorRecords; | |
public override string ToString() | |
=> string.Join(Environment.NewLine, _errorRecords.Select(x => x.ToString())); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment