Sysprep an Azure VM using PowerShell task in a pipeline - azure

My (dotNET) application is built (using a Windows Hosted agent), from a build pipeline, and in the subsequent Release pipeline, I provision a 16GB-Win2016 VM (enabling RDP, HTTP, HTTPS, WinRM and SSH), into which I RDP manually (there is a Manual Intervention task here), and configure WinRM (following this article: https://learn.microsoft.com/en-us/azure/marketplace/cloud-partner-portal/virtual-machine/cpp-configure-winrm-after-vm-creation#configure-vm-to-enable-winrm). Everything is fine until here. The next task is a Azure File Copy task, which essentially copies the Build artifacts (from $(System.DefaultWorkingDirectory)) and pastes into a directory I specify. Works like a charm. The next task I have is to create a VHD of this whole VM (essentially after the copying is done).
I know I can manually RDP into the VM (again) and sysprep (with oobe/generalize/shutdown), then maybe go back to the Azure Portal and Disk Export the OS Disk (specifying the SAS URL expiration time at whatever (36000 per the article)) BUT can this all be automated?
So, long story short - I'd like to know if sysprep oobe/generalize/shutdown can be performed remotely preferably over a PS task. I understand the other part of it (exporting the disk and all) can be, but if sysprep can be done remotely nothing like it.

I tried this and got what I wanted:
$sysprep= 'C:\Windows\System32\Sysprep\Sysprep.exe'
$arg1 = '/generalize'
$arg2 = '/oobe'
$arg3 = '/shutdown'
$arg4 = '/quiet'
& $sysprep $arg1 $arg2 $arg3 $arg4 -Wait

Make sure you do NOT use Azure custom script extension to run sysprep.
Azure scripts run under the LocalSystem user context: source
Custom Script Extension will run under the LocalSystem Account
This is problematic because sysprep does NOT support running under a system user context: source
Sysprep cannot be run under the context of a System account. Running Sysprep under the context of System account by using Task Scheduler or PSExec, for example, is not supported.
Providing this so that people avoid my mistake :)

So, you dont have to configure winrm manually, you can script it\configure it while provisioning the vm. and if\when winrm is working you can just use powershell remoting to issue a command against the vm with:
Invoke-Command -ComputerName dnsname\ipaddress_goes_hehe
-ScriptBlock { sysprep /shutdown /generalise}
https://github.com/Azure/azure-quickstart-templates/tree/master/201-vm-winrm-windows

You can implement this using an Azure custom script extension. There is a github project:
https://github.com/jlongo62/AzureVMToImage containing powershell scripts to image a VM. These scripts were built to preserve VM when creating an image, instead of destroying the original VM. The scripts can be called from Azure Devops. There is no need to authenticate against the VM.
The meat of what you need is:
1- create a storageaccount blob containing the following script (the -Wait is very important):
Start-Process -FilePath C:\Windows\System32\Sysprep\Sysprep.exe -ArgumentList '/generalize /oobe /quiet /quit' -Wait
2 - invoke it on the VM:
$response = Set-AzureRmVMCustomScriptExtension `
-ResourceGroupName $vm.ResourceGroupName `
-VMName $vm.Name `
-Location $vm.Location `
-Name $ExtensionName `
-FileUri $blobUri `
-Run $FileName

Related

Sysprep Azure VM with Remote PowerShell

I am trying to SysPrep a VM in Azure via Azure DevOps YAML pipelines. In the same pipeline (previous stage), I am deploying the VM via Azure Resource Manager (ARM) templates and copying scripts and files to the machine. I want to automate the whole process so RDP'ing into the machine is not an option. When I run SysPrep on the VM, I am getting the following error message:
##[error]The remote session query failed for <insertMyFQDN>.eastus.cloudapp.azure.com with the following error message: Access is denied.
From my repo I have a couple of files, including a PowerShell script that SysPreps the machine (sysPrepvm.ps1) - taken from Run SysPrep remotely through commands from Azure powershell. The script works when I am logged in on the machine and run in manually.
sysPrepvm.ps1
$sysPrepPath = 'C:\Windows\System32\Sysprep\Sysprep.exe'
$arguments = '/generalize /oobe /shutdown /quiet'
Invoke-Command -ScriptBlock {param($sysPrepPath,$arguments) Start-Process -FilePath $sysPrepPath -ArgumentList $arguments} -ArgumentList $sysPrepPath,$arguments
I am using the built-in task in Azure DevOps, "Powershell on Target Machines", with which I am able to invoke other commands or scripts, so I am confident that the task works.
My Stage in the YAML Pipeline looks like this:
- stage:
displayName: SysPrep
variables:
azFQDN: $[stageDependencies.Connect.connect.outputs['SetEnvVars.azFQDN']]
jobs:
- job: SysPrepVhD
steps:
- task: PowerShellOnTargetMachines#3
inputs:
Machines: '$(azFQDN)' # FQDN on the machine
UserName: '$(adminUser)'
UserPassword: '$(adminPw)'
ScriptType: 'FilePath'
ScriptPath: 'C:\Windows\System32\Sysprep\fishtank\sysPrepvm.ps1'
Is there a limitation of running this remotely? I haven't been able to find a work-around yet, so any answer is greatly appreciated.
Edit
I have also tried running the script with -Verb RunAs, as an Inline script instead of File, as well as tried the accepted answer on the previous post:
Sysprep an Azure VM using PowerShell task in a pipeline
I managed to find a way using Custom Script Extension instead. I didn't think of doing this at first, since the ARM template I am using already has a Custom Script Extension on the machine to enable WinRM, and cannot have multiple Custom Script Extensions installed at the same time. Also, I didn't want to execute SysPrep as soon as the machine booted because it had to run other scripts and settings first. In my YAML pipeline, I managed to remove the existing one to redeploy a new Custom Script Extension with the script included.
I posted the entire solution on GitHub - https://github.com/Crytlig/azsysprep - for anyone in interested. It obviously needs a bit of cleaning but it works as is.

How to run "sysprep /generalize” in Azure Windows Virtual Machine (VM) from my local machine using Powershell?

I have a windows Azure VM and need to execute “%windir%\system32\sysprep” and then execute “sysprep /generalize” both from admin mode from my local machine through Powershell. How can I do that ?
For your requirements, as I know you can use a PowerShell script to achieve it. First, you can take a look at the Sysprep, it can be run in a PowerShell command C:\WINDOWS\system32\sysprep\sysprep.exe /generalize /shutdown /oobe. Put this command inside a script, then you can use two ways to run this script in the VM from your local machine. One is that use the Invoke command.
In Azure CLI:
az vm run-command invoke --command-id RunPowerShellScript -g group_name -n vm_name --scripts #script.ps1
In PowerShell:
Invoke-AzVMRunCommand -ResourceGroupName 'rgname' -VMName 'vmname' -CommandId 'RunPowerShellScript' -ScriptPath 'sample.ps1'
Another is that use the VM extension. It's a little complex. You can take a look at the Azure PowerShell command Set-AzVMCustomScriptExtension.
Output after running:-
Value[0] :
Code : ComponentStatus/StdOut/succeeded
Level : Info
DisplayStatus : Provisioning succeeded
Message :
Value[1] :
Code : ComponentStatus/StdErr/succeeded
Level : Info
DisplayStatus : Provisioning succeeded
Message :
Status : Succeeded
Capacity : 0
Count : 0
I could't make sysprep work with Invoke-AzVMRunCommand, It run with succeeded status, but the VM was not shutdown.
Finally found https://developercommunity.visualstudio.com/t/devops-sysprep-public-agents/1375989 and it make sense.
So just use Invoke-AzVMRunCommand to run sysprep won't work, I am thinking to reset a local admin user password and run the process as local admin might be a workaround.

Azure runbook automation - Invoke-AzureRmVMRunCommand error

I have an Azure runbook that runs on schedule. Its in powershell and this runbook starts a VM and executes a script on the VM started. How I achieve this is
1) Store the script to be run on the VM in a storage account
2) Run powershell runbook
3) Powershell runbook uses wget command to copy the script from step 1
4) Invoke-AzureRmVMRunCommand in the Azure automation powershell commands as shown below
wget "https://utilitystorageaccnt.blob.core.windows.net/utilitycontainer/token" -outfile ((Get-Location).path + "\Reporting Copy.ps1") -UseBasicParsing
Invoke-AzureRmVMRunCommand -ResourceGroupName $ResourceGroupName -VMName $VmName -CommandId 'RunPowerShellScript' -ScriptPath ((Get-Location).path + '\Reporting Copy.ps1') -ErrorVariable result
Please not that the above two commands are in the powershell runbook script and not the actual script that is run on the VM.
Facing two issues
1) When this script Reporting Copy.ps1 runs standalone on the VM, then it works properly and it has no issues. When it is run using the runbook, I get these errors in the log file on the target vm.
"New-AzStorageContext : The term 'New-AzStorageContext' is not recognized as the name of a cmdlet, function, script
file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct
and try again."
2) Even after this error occurs, it doesnt terminate and runs in loops. This script does a copy operation and it keeps looping until all the copy is complete. I can handle code to terminate but I would like to know how to force terminate a runbook. I tried to stop the VM for even a hour and it resumes the copy operation. The runbook status in Azure shows as completed. There are two python processes that show in explorer and terminating them doesn't work either.
Any help or hint is appreciated.
Thanks.
Look like you did not imported Az PowerShell module into our Automation Account.
Please, follow this tutorial : Az module support in Azure Automation
Try to use only Az module and not AzureRM
The issue was because I had not installed the AZ module for all users like this.
Install-Module -Name Az -AllowClobber -Scope AllUsers
Instead I had used
Install-Module -Name Az -AllowClobber -Scope CurrentUser
and since the automation runs on a different user, the issue occurred. Thanks for your help.

Update-AzureRmVmss command is not working

I created azure vm scale set with custom script extension. After some time I updated custom script extension and wanted to apply changes on each virtual machine on scale set. However nothing is changing. To update VM Scale Set I used Update-AzureRmVmss command in powershell. There is not any error message, command executes successfully
I would say you VMSS is set to update mode manual, so you need to force the node update.
https://learn.microsoft.com/en-us/powershell/module/azurerm.compute/update-azurermvmssinstance?view=azurermps-6.4.0
another thing you are missing: forceUpdateTag. check this: How to force VM created with an ARM template + CustomScriptExtension to redownload the script and run it?
It sounds like you need to update the "forceUpdateTag" property in the scale set extension profile. You can do this in your ARM template by adding this property at the same level as "publisher" and giving it any value you want (as long as it's different from the previous value). You can also do the same in Powershell and Az CLI using code like the following:
Powershell:
$vmss = Get-AzureRmVmss -ResourceGroupName YOUR_RG_NAME -Name YOUR_VMSS_NAME
$vmss.VirtualMachineProfile.ExtensionProfile.Extensions[0].ForceUpdateTag="1"
Update-AzureRmVmss -ResourceGroupName YOUR_RG_NAME -VMScaleSetName YOUR_VMSS_NAME -VirtualMachineScaleSet $vmss
CLI:
# run the first time
az vm extension set -g ...
# run again
az vm extension set --force-update -g ...
It's a bit complicated in some cases, so I threw together a quick blog post describing it:
https://negatblog.wordpress.com/2018/07/11/rerun-extensions-in-azure/
Hope this helps! :)
-Neil

Setting script to run at user login on Azure VM via ARM

I have generalized a windows Azure image and I am using it in an ARM (Azure resoure manager) template to create a new VM. At the time of VM creation I am doing some bootstrapping work using Custom Script extension that runs a Powershell script. It works fine except for few things that are specific to a user that will log on to machine. For example, I can't map a network drive or set powershell exection policty to remotesigned for the user who will log in to the machine via Custom Script Extension.
Is there a way to accomplish user specific tasks in the ARM template or set some scripts to run when user will log in?
Maybe a bit late, but I am using sth similar to this:
However, we can’t directly hand this script to Custom Script Extension yet. Custom Script Extension uses NTAUTHORITY\SYSTEM account to execute scripts, but the account doesn’t have necessary privileges to execute the SQL configuration commands. To work around this, we’ll create a separate bootstrap script, which impersonates an administrator account before invokes the script.
Here is the code used:
$password = ConvertTo-SecureString "[your admin account user password]" -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential("$env:COMPUTERNAME\[your admin account]", $password)
$command = $file = $PSScriptRoot + "\CustomScriptSQLPS.ps1"
Enable-PSRemoting –force
Invoke-Command -FilePath $command -Credential $credential -ComputerName $env:COMPUTERNAME
Disable-PSRemoting -Force
I don't know if remoting is relevant for this though ...
Credits to Haishi Bai

Resources