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.xml

Although 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:\Windows\Panther\Unattend.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:



what the New-OSCustomizationSpec  actually returned was:

(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 powercli and unattend.xml (via SysPrepText)

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:


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:

  1. 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).

  1.  Create the CustomizationSpecItem

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

4. Add the CustomizationSpecInfo to the global CustomizationSpecItem (Created in step 2)


5. Create the CustomizationSpecOptions

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)

7. Create a Network card (At least one NIC is needed)

8. Adding the IP Configuration to the nic

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.

It is possible to add multiple nics

10. Add the new customizationSpec to the CustomizationSpecItem (Created in Step 2)

11. Create the customizationSpec using the CustomizationSpecManager

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:

Or directly using the official “Get-OsCustomizationSpec” cmdlet

13. Deploying the VM using a unattend.xml, the new OsCustomizationSpec we created, and the New-VM powercli cmdlet


Full code listing:

The full code listing is available here under


I hope this would have helped anybody to deploy a vm using powercli and unattend.xml

By | 2018-03-04T14:50:30+00:00 February 23rd, 2018|OSD, PowerCLI, PowerShell, VMware|2 Comments

About the Author:

Stéphane is a dynamic and passionate Cloud and datacenter Microsoft MVP since. He is the founder of the Basel PowerShell user Group (BPUG), the co-founder of the French Speaking PowerShell UserGroup (FRPSUG), author, blogger, and received the community award "PowerShell Hero" from Stéphane has implemented microsoft infrastructure solutions in various countries of Europe and is currently working in Basel / Switzerland. Stéphane help his clients to reduce their global infrastructure costs by implementing Microsft infrastructure solutions by combining great products such as System Center, Windows Server, with heavy automation using Windows PowerShell. Stéphane loves languages, Belgium beer, French cheese and French Wine. If any of these topics are of your interest, don't hesitate to come and say hi.


  1. mike dopp (@mikedopp) March 15, 2018 at 4:48 pm - Reply

    Great article. I was hoping to find a way to inject the OSCustomSpec into a ISO build instead of a templated build. However I did enjoy this. Thank you.

    • Stephane March 15, 2018 at 5:35 pm - Reply

      hi Mike,
      glad you found the article usefull 🙂

      what do you mean by a Iso Build?

Leave a Reply

%d bloggers like this: