Recently I blogged how to deploy a VM on VMware platforms (such as Vsphere) using PowerCLI, and more specifically the New-OScustomizationSpec. I mentioned in that article, that indeed the New-OSCustomizationSpec helps to create a new customizationspec, and give us the possibility to add customization to our Windows (or linux) deployment(s). In this article I’ll cover how to deploy a VM using PowerCLI with a unattend.xmlAlthough the documentation seems to offer a range of parameters to customize our Windows Deployments, this could turn out to be not enough. I faced a situation where I needed a more granularity, and have been somewhat blocked original range of the New-OSCustomizationSpec cmdlet. Another pain point I suffered from is the lack of updated documentation about some of the switches / properties that are available in that cmdlet. (See here for more information).
–> DeleteUser is deprecated (Although not really clear) -> (Link)
–> ChangeSID is Mandatory (Although documented as not beeing mandatory) -> (link)
Although I managed to had a nice and working solution in the end, I have to confess that this inconsistency (which took me a few hours to find out) reduced the trust I had in this Cmdlet. (More specially, in his parameters). And I am afraid that some other things could potentially be different then what has been documented – which means to me – broken.
A deployment method that has worked since – forever – is the one passing a unattend.xml file to the setup.exe of Windows, by either passing as a parameter to the setup.exe or by setting the unattend.xml file in the Panther folder. (btw, does anyone knows why the panther folder is called the Panther folder, and not “deployment” or so?).
We know that under the hoods, the New-OSCustomizationSpec actually generates a unattend.xml file. This unattend.xml document can be found on any deployed machine with that OScustomizationSpec on the following path: C:WindowsPantherUnattend.xml
All the parameters we have set using the various parameters of the New-OsCustomizationSpec (And also the settings set via New-OSCustomizationNicMapping) are visibile in that file.
My Original need: Deploy a VM using powercli using an unattend.xml:
The New-OSCustomizationSpec cmdlet comes with a neat parameter called “GUIRunOnce”. As it’s name suggests, it allows to call a script using the RunOnce Key. This is quite convenient and actually works pretty well. But of course, I had a case where this would not be enough (Unfortunatley)!
Indeed, I my scenario, I would deploy my VM using a template, which came with a (very small) script located in the following path C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup . This script would contact a master server to download it’s specific configuration and apply it. As I mentionned earlier, when using the New-OsCustomizationSpec although it is not mentioned, the –ChangeSID parameter is mandatory. This parameter has the same effect as the sysprep.exe /generalize which, you might know, will delete all the OS customizations that has been done to the Windows OS, and set it to the factory defaults. This actually meant, that my Administrator user folder was deleted when I used that command, which was really a bummer for me.
The first Idea I had, was to simply Copy the file to that location using the GuiRunonce parameter. Unfortunatley, that also didn’t work, since the command seemed to be called to early when used using the GuiRunOnce parameter of the OSCustomizationSpec.
I needed the following Runonce:
1
2
3
4
5
6
7
|
<LogonCommands>
<<strong>AsynchronousCommand</strong> wcm:action=“add”>
<CommandLine>c:MyScriptsMyPostStagingScript.cmd</CommandLine>
<Description>Launch of the first logon Script</Description>
<Order>1</Order>
</<strong>AsynchronousCommand</strong>>
</LogonCommands>
|
what the New-OSCustomizationSpec actually returned was:
1
2
3
4
5
6
7
|
<LogonCommands>
<<strong>synchronousCommand</strong> wcm:action=“add”>
<CommandLine>c:MyScriptsMyPostStagingScript.cmd</CommandLine>
<Description>Launch of the first logon Script</Description>
<Order>1</Order>
</<strong>synchronousCommand</strong>>
</LogonCommands>
|
(Synchronous / Asynchronus)
This one particular case forced me to reconsider my entier approach of the New-OSCustomizationSpec . And this is why I switched to the SysPrepText approach instead of the New-OsCustomization one.
Deploying a VMware VM using powercli and unattend.xml
Using SysPrepText to customize your unattend.xml in powercli
Ok, enough of this story telling, I know you are here to find answers, not to listen to stories.
I found out about the The VCCustomizationSysprepText object through this post here and found there the salvation, a new hope.
After really quite some big struggle, and some great hints of a member of the French PowerShell User Group Nicolas Baudin, I managed to make a workable solution, which answered all my issues above, and it simply made my day 😉
The most difficult part of this solution, is not even the technical part, as you will see in a minute, once you read the code you will be like: “Ha yeah, easy!” (But keep in mind, reading code that exists is much easier then writing code that doesn’t exists. Some people tend to forget that sometimes…). But in was really in the process of finding out how the heck this all works together, since VMWare didn’t document it enough to make things easy for us. (but that is the big why of this blog post actually).
There is not a big amount of documentation that exists on the CustomizationSysprepText property, but keep in mind, that it is a child property the more global CustomizationSpecItem type.
TI am not going to go through how I managed to find how all of these things were connected together, take this a late (or early) christmas present, as I know that this part was the difficult part to find out:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#CustomizationSpecItem
#CustomizationSpec
#GlobalIPSettings
#None (GlobalIPSettings) (->Can be an empty object)
#Identity (CustomizationIdentitySettings)
#CustomizationLinuxPrep
#CustomizationSysprep
#customizationSysprepText
#value (String)
#CustomizationSpecInfo (CustomizationSpecInfo)
#Name (String)
#Description (string)
#type (string)
#New CustomizationSpec
|
Ok, what is that, you might ask? This is a ‘note’ I added Well it is simply how the CustomizationSpecItem should be filled. This means that, each property must have an object of a specific type. Some properties are mandatory. some properties MUST have an object having some mandatory fields set, and some properties are not mandatory but they must have at least and object instanciated of that child type (that object can be empty).
In the section above, I try to show which objects are mandatory, and which are not. The imbrication of objects goes up to 6 steps down, and clearly, this can quickly become something confusing. (kind of like this article in general, but even worse!)
Generating the customizatinospec will need to be cut in a few steps:
-
Creating the SysPrepfile (unattend.xml)
As this is always specific to how an organization decides how to implement their OS, I won’t cover here how to create one today (but it might come another time).
- Create the CustomizationSpecItem
1
2
3
4
5
6
7
8
9
|
$SpecName = “Spec_$($BuildInfos.VMName)”
$CustomSpecItem = New-Object VMware.Vim.CustomizationSpecItem
$NewVMSpec = New-Object VMware.Vim.CustomizationSpec
$GlobalIpSettings = New-Object VMware.Vim.CustomizationGlobalIPSettings
$NewVMSpec.GlobalIPSettings = $GlobalIpSettings
$SysprepText = New-Object VMware.Vim.CustomizationSysprepText
$SysprepText.value = $FullSysprep #SysPrepText Is applied here
$NewVMSpec.Identity = $SysprepText
|
The above properties all need to be set and instantiated prior to execute the steps here under, otherwise, you won’t be able to add the newly created objects to the main CustomizationSpec / CustomizationSpecItem
3. Create the CustomizationSpecInfo
1
2
3
4
|
$SpecInfo = New-Object VMware.Vim.CustomizationSpecInfo
$SpecInfo.Name = $SpecName
$SpecInfo.Description = “Spec for $($BuildInfos.VMName)”
$SpecInfo.Type = “Windows”
|
4. Add the CustomizationSpecInfo to the global CustomizationSpecItem (Created in step 2)
1
|
$CustomSpecItem.Info = $SpecInfo
|
5. Create the CustomizationSpecOptions
1
2
3
|
$SpecOptions = New-Object VMware.Vim.CustomizationWinOptions
$SpecOptions.changeSID = 1 #mandatory otherwise it will fail (0 will not work).
$SpecOptions.DeleteAccounts = 0 #Deprecated, thus 1 will actually NOT work
|
Of course, if you are deploying Linux machine, you will need to instantiate the VMWare.Vim.CustomizationLinuxOptions class (but then you won’t need the unattend.xml file either ;))
6. Add the CustomizationSpecOptions to the Global CustomizationSpecItem (Created in step 2)
1
|
$NewVMSpec.Options = $SpecOptions
|
7. Create a Network card (At least one NIC is needed)
1
2
|
#Nic Adapter
$Nic = New-Object VMware.Vim.CustomizationAdapterMapping
|
8. Adding the IP Configuration to the nic
1
2
3
|
$NicSettings = new-object vmware.vim.CustomizationIPSettings
$NicSettings.Ip = new-object vmware.vim.CustomizationDhcpIpGenerator
$Nic.Adapter = $NicSettings
|
The IP configuration can be set to either Static, or DHCP. One of the following classes will need to be instantiated.
–> DHCP -> vmware.vim.customization.DhcpIpGenerator
–> Static –> vmware.vm.customization.FixedIp
9. Adding the created NIC (with its IPConfiguration added in step 8) to the global CustomizationSpec created in step 2.
1
2
|
#Adding Nic To spec
$NewVMSpec.NicSettingMap = $nic
|
It is possible to add multiple nics
10. Add the new customizationSpec to the CustomizationSpecItem (Created in Step 2)
1
|
$CustomSpecItem.Spec = $NewVMSpec
|
11. Create the customizationSpec using the CustomizationSpecManager
1
2
|
$view = get-view CustomizationSpecManager
$view.CreateCustomizationSpec($CustomSpecItem)
|
This step is actually THE step that we have been waiting for. It creates that one beloved and desired OsCustomizationSpec, holding our Unattend.xml file (via the SysPrepText property) that we have actually tried to use since the begining of this article (I bet you kind of forgot about it, with all of this stuff we needed to do).
12. Get and use the OsCustomizationSpec that holds our Unattend.xml
it is possible to get our CustomizationSpec using the ConstomizationSpecManager (via the view) like this:
1
|
$NewSpec = $view.GetCustomizationSpec($SpecName)
|
Or directly using the official “Get-OsCustomizationSpec” cmdlet
1
2
|
#Getting the Spec. This spec is the spec needed by the New-VM cmdlet.
$NewSpec = Get-OSCustomizationSpec -Name $SpecName
|
13. Deploying the VM using a unattend.xml, the new OsCustomizationSpec we created, and the New-VM powercli cmdlet
1
2
3
4
5
6
7
|
NewVM = new-VM -Name $BuildInfos.VmName
-Template $BuildInfos.Template
-VMHost $VMhost
-Datastore $DatastoreCluster.Name
-Location $Buildinfos.VMFolder
-OSCustomizationSpec $NewSpec
-ErrorAction Stop
|
Full code listing:
The full code listing is available here under
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
#New CustomizationSpec
$SpecName = “Spec_$($BuildInfos.VMName)”
$CustomSpecItem = New-Object VMware.Vim.CustomizationSpecItem
$NewVMSpec = New-Object VMware.Vim.CustomizationSpec
$GlobalIpSettings = New-Object VMware.Vim.CustomizationGlobalIPSettings
$NewVMSpec.GlobalIPSettings = $GlobalIpSettings
$SysprepText = New-Object VMware.Vim.CustomizationSysprepText
$SysprepText.value = $FullSysprep #SysPrepText Is applied here
$NewVMSpec.Identity = $SysprepText
#Infos about the Customization
$SpecInfo = New-Object VMware.Vim.CustomizationSpecInfo
$SpecInfo.Name = $SpecName
$SpecInfo.Description = “Spec for $($BuildInfos.VMName)”
$SpecInfo.Type = “Windows” # added
$SpecOptions = New-Object VMware.Vim.CustomizationWinOptions
$SpecOptions.changeSID = 1 #mandatory otherwise it will fail (0 will not work).
$SpecOptions.DeleteAccounts = 0 #Deprecated, thus 1 will actually NOT work
$NewVMSpec.Options = $SpecOptions
#Nic Adapter
$Nic = New-Object VMware.Vim.CustomizationAdapterMapping
$NicSettings = new-object vmware.vim.CustomizationIPSettings
$NicSettings.Ip = new-object vmware.vim.CustomizationDhcpIpGenerator
$Nic.Adapter = $NicSettings
#Adding Nic To spec
$NewVMSpec.NicSettingMap = $nic
#IpSettings
$CustomSpecItem.Info = $SpecInfo
$CustomSpecItem.Spec = $NewVMSpec
$view = get-view CustomizationSpecManager
$view.CreateCustomizationSpec($CustomSpecItem)
$NewSpec = $view.GetCustomizationSpec($SpecName)
#Getting the Spec. This spec is the spec needed by the New-VM cmdlet.
$NewSpec = Get-OSCustomizationSpec -Name $SpecName
NewVM = new-VM -Name $BuildInfos.VmName
-Template $BuildInfos.Template
-VMHost $VMhost
-Datastore $DatastoreCluster.Name
-Location $Buildinfos.VMFolder
-OSCustomizationSpec $NewSpec
-ErrorAction Stop
|
I hope this would have helped anybody to deploy a vm using powercli and unattend.xml
1 |
Write-host "See in the next article :)" |
Leave A Comment