Skip to content

Instantly share code, notes, and snippets.

@rkttu
Last active April 29, 2024 05:39
Show Gist options
  • Save rkttu/1da8ff831c205eb6850ce41dc0e1dad5 to your computer and use it in GitHub Desktop.
Save rkttu/1da8ff831c205eb6850ce41dc0e1dad5 to your computer and use it in GitHub Desktop.
Create Azure Windows VM, Connect WinRM, RDP with LINQPad (Demo)
/*
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