PowerShell is King – Building a Reference Image Factory (v 3.2)

The Image Factory has been re-worked and updated. It also has moved to GitHub

Concept and flow

The idea is the same, we use MDT, create refimages task sequences that runs without any questions. A PowerShell script will grab all enabled Task Sequences in a specified folder, create a VM for each of them. Grab the Bios Serial number from each om them, update customsettings ini with that information, start the VM’s (number of concurrent VM’s determine how many VM’s can run at the same time, wait until all VM’s are done and finally removes all VM’s for a cleanup process. You can run the script as is (if you store it in C:\Setup\ImageFactoryV3ForHyper-V on you MDT server), or you can open it in ISE and run section by section to see what happens.

The XML file (C:\Setup\ImageFactoryV3ForHyper-V\ImageFactoryV3.xml)

Is a control file for settings, pretty straightforward, just make sure that all values are correct.


The Script C:\Setup\ImageFactoryV3ForHyper-V\ImageFactoryV3-Build.ps1

The script contains some functions and logic, each section is described so you do know what it does. There are switches to the script,

  • -UpdateBootImage
  • -EnableMDTMonitoring
  • -TestMode

UppdateBoot image $True, updates the bootimage before copy, EnableMDTMonitoring require that MDT monitoring is enabled of the deployment share and testmode is not yet working (I’ll get back on that)


The PSINI Module

One big change is that instead of using a hack, (using KVP) I switched over to use a module called PSINI, it basically turns customsettings.ini into a hash table, making it easy to configure using PowerShell. That also means you need to download that module from TechNet Gallery all credit for that module goes to

Have Fun


26 replies »

  1. Hi Mikael,

    I have an issue, when i try to load the Customsettings.ini file, do you know what could be wrong ? if i run “Get-IniContent E:\DeploymentLAB\Control\CustomSettings.ini” it’s loading the values just fine?

    Below is the error I’m seeing.

    • I “think” it is because the name of your TS contains a .
      “OSDComputerName=W10EN-US.O2016EN”, therefore it is not a string, I can test it later…unless you test it first

      • I just tried with a TS with TS ID: REFWS2016 with no “-” or “.” – same error

        Set-IniContent : Cannot process argument transformation on parameter ‘NameValuePairs’. Cannot convert the “OSDComputerName=REFWS2016” value of type “System.String” to type “System.Collections.Hashtable”.
        At C:\Scripts\MDT\ImageFactoryV3ForHyper-V\ImageFactoryV3-Build.ps1:365 char:100
        + … NameValuePairs “OSDComputerName=$Ref”
        + ~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : InvalidData: (:) [Set-IniContent], ParameterBindingArgumentTransformationException
        + FullyQualifiedErrorId : ParameterArgumentTransformationError,Set-IniContent

    • Same error, it happens with this code:

      #Get BIOS Serialnumber from each VM and update the customsettings.ini file
      Update-Log -Data “Get BIOS Serialnumber from each VM and update the customsettings.ini file”
      $BIOSSerialNumbers = @{}
      Foreach($Ref in $RefTaskSequenceIDs){

      #Get BIOS Serailnumber from the VM
      $BIOSSerialNumber = Invoke-Command -ComputerName $($Settings.Settings.HyperV.Computername) -ScriptBlock {
      $VMObject = Get-WmiObject -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter “ElementName = ‘$VMName'”
      } -ArgumentList $Ref

      #Store serialnumber for the cleanup process

      #Update CustomSettings.ini

      $IniFile = “$($Settings.settings.MDT.DeploymentShare)\Control\CustomSettings.ini”
      $CustomSettings = Get-IniContent -FilePath $IniFile -CommentChar “;”

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “OSDComputerName=$Ref”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “TaskSequenceID=$Ref”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “BackupFile=$Ref.wim”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “SkipTaskSequence=YES”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “SkipApplications=YES”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “SkipCapture=YES”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

      if($TestMode -eq $True){
      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “DoCapture=NO”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate
      $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “DoCapture=YES”
      Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

  2. Everything up until this code, works fine:

    $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs “OSDComputerName=$Ref”
    Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

  3. Got it working :)

    Just change to this :

    $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs @{‘OSDComputerName’=$Ref ; ‘TaskSequenceID’=$Ref ;’BackupFile’= “$Ref.wim” ; ‘SkipTaskSequence’=’YES’ ;’SkipApplications’=’YES’ ; ‘SkipCapture’=’YES’ }
    Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputObject $CSIniUpdate

  4. Hi Mikael,

    cool stuff, but when setting SkipTaskSequence=Yes
    LiteTouch.wsf isn’t able to copy TS.xml cause oEnvironment.Item(“TaskSequenceID”) is empty.
    Where’s the trick ;-?

    Regards Markus

  5. I am having a similar issue to what Janick was having, however his solution is not working for me.

    I remember this was working a couple of weeks ago, and now it isn’t not sure why. Failing on this code here.

    Get-IniContent : A parameter cannot be found that matches parameter name ‘CommentChar’.
    At C:\Setup\ImageFactoryV3ForHyper-V\ImageFactoryV3-Build.ps1:362 char:57
    + … $CustomSettings = Get-IniContent -FilePath $IniFile -CommentChar “;”
    + ~~~~~~~~~~~~
    + CategoryInfo : InvalidArgument: (:) [Get-IniContent], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Get-IniContent
    Set-IniContent : The term ‘Set-IniContent’ 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.
    At C:\Setup\ImageFactoryV3ForHyper-V\ImageFactoryV3-Build.ps1:390 char:24
    + $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$ …
    + ~~~~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (Set-IniContent:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

    Out-IniFile : The term ‘Out-IniFile’ 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.
    At C:\Setup\ImageFactoryV3ForHyper-V\ImageFactoryV3-Build.ps1:391 char:9
    + Out-IniFile -FilePath $IniFile -Force -Encoding ASCII -InputO …
    + ~~~~~~~~~~~
    + CategoryInfo : ObjectNotFound: (Out-IniFile:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

      • You are absolutely right. Looks like at some point in time I must have made a bad move and deleted all of the supporting files and only had the PSINI.psm1 module. Resynced the git project and it is working now.

      • Could you add a Verify for this and a setting in the XML for the location of the PSINI folder?

    • I am having the same issue, and that’s because the PSIni.psm1 module i have downloaded from TechNet gallery is maybe not up to date. In the Get-IniContent function, there is no “-CommentChar” parameter, so. this error is throw.

  6. Mikael,

    Thank you for such an awesome solution. I am running into one issue with the process.

    I was originally having issues with the out-inifile not being recognized when I realized that the link in your article takes you directly to “get-inicontent” instead of the entire PSINI suite (unless i’ve missed something). After downloading the entire PSINI module, I am encountering the following error with updating customsettings.ini:

    WARNING: Update-IniEntry:: Unable to split ‘System.Collections.Hashtable’ into a distinct key/value pair.

    Any advice is appreciated, please let me know if you need more information.

  7. Just a quick heads up. The script is piping in the Task sequence id as the OSDComputername.. The character limit on the OSDComputername property is 15. The limit on the Task Sequence ID is 16. :-) So if I had task sequences with the 16 character limit, when applying the unattend.xml I was getting the following error.

    Windows could not parse or process unattend answer file [C:\windows\Panther\unattend.xml] for pass [specialize]. The answer file is invalid.

    this error is also in the setupact.log

    2017-04-21 14:53:10, Error [setup.exe] SMI data results dump: Source = Name: Microsoft-Windows-Shell-Setup, Language: neutral, ProcessorArchitecture: x86, PublicKeyToken: 31bf3856ad364e35, VersionScope: nonSxS, /settings/ComputerName
    2017-04-21 14:53:10, Error [setup.exe] SMI data results dump: Description = Value is invalid.

    Once I changed my Task Sequence ID’s to the 15 character limit, everything worked just fine. Thanks a lot Mikael for the awesome Image Factory!

    p.s. thanks to Mike Niehaus for helping me pinpoint the error above.

  8. Great work Mikael. I used the previous version with great success, I missed having the date in the capture vm so added the following code to your script:

    #Update CustomSettings.ini
    $FDATE = “””_#year(date)” + ” & ” + “””” + “-” +”””” + ” & ” + “day(date)” + ” & “+ “””” + “-” + “””” +” & “+ “month(date)” + “#”””
    $CSIniUpdate = Set-IniContent -FilePath $IniFile -Sections “$BIOSSerialNumber” -NameValuePairs @{“BackupFile”=”$Ref$FDATE.wim”}

  9. Hi Mikael,

    First of all, awesome script!

    I have a slight problem though, the scripts creates the VM and start the TS, but when running the scripts it immediately crashes because it can find the wim file.
    Look at the time frame, it’s like it does not get any input from the hyper-v server exactly like your screenshot in the main post – what could be wrong ?

    The log:

    IMF32, Imagefactory 3.2 (Hyper-V), 08/18/2017 08:40:09
    IMF32, Logfile is E:\Scripts\MDT\ImageFactoryV3ForHyper-V\log.txt, 08/18/2017 08:40:09
    IMF32, XMLfile is E:\Scripts\MDT\ImageFactoryV3ForHyper-V\ImageFactoryV3.xml, 08/18/2017 08:40:09
    IMF32, Importing modules, 08/18/2017 08:40:09
    IMF32, Reading from E:\Scripts\MDT\ImageFactoryV3ForHyper-V\ImageFactoryV3.xml, 08/18/2017 08:40:09
    IMF32, Verify Connection to DeploymentRoot, 08/18/2017 08:40:09
    IMF32, Connect to MDT, 08/18/2017 08:40:09
    IMF32, Connected to E:\DeploymentLab, 08/18/2017 08:40:09
    IMF32, Get MDT Settings, 08/18/2017 08:40:09
    IMF32, Check if we should update the boot image, 08/18/2017 08:40:09
    IMF32, Check if we should use MDTmonitoring, 08/18/2017 08:40:09
    IMF32, Verify access to boot image, 08/18/2017 08:40:09
    IMF32, Access to E:\DeploymentLab\boot\LiteTouchPE_x86.iso is ok, 08/18/2017 08:40:09
    IMF32, Get TaskSequences, 08/18/2017 08:40:09
    IMF32, Found 1 TaskSequences to work on, 08/18/2017 08:40:09
    IMF32, Get detailed info about the task sequences, 08/18/2017 08:40:09
    IMF32, WIN10PROX64 Win10ProX64 1.0, 08/18/2017 08:40:09
    IMF32, Verify Connection to Hyper-V host, 08/18/2017 08:40:09
    IMF32, Upload boot image to Hyper-V host, 08/18/2017 08:40:21
    IMF32, Remove old WIM files in the capture folder, 08/18/2017 08:40:23
    IMF32, Create the VM’s on Host, 08/18/2017 08:40:23
    IMF32, Get BIOS Serialnumber from each VM and update the customsettings.ini file, 08/18/2017 08:40:28
    IMF32, Start VM’s on Host, 08/18/2017 08:40:29
    IMF32, ConcurrentRunningVMs is set to: 1, 08/18/2017 08:40:29
    Currently running VM’s : WIN10PROX64 at 08/18/2017 08:40:33

    IMF32, Wait until they are done, 08/18/2017 08:41:03
    IMF32, Cleanup VMs, 08/18/2017 08:41:35
    IMF32, Update CustomSettings.ini, 08/18/2017 08:41:36
    IMF32, Cleanup MDT Monitoring data, 08/18/2017 08:41:36
    IMF32, Show the WIM’s, 08/18/2017 08:41:36
    IMF32, Could not find E:\DeploymentLab\Captures\WIN10PROX64.wim, something went wrong, sorry, 08/18/2017 08:41:36
    IMF32, The script took 0:Days 0:Hours 1:Minutes to complete., 08/18/2017 08:41:36

Leave a Reply to Markus Cancel reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.