Updated Dell Driver Catalog script

So I provided feedback to Dell last year about the schema on their DriverPackCatalog.xml file. I wanted to tie the Make & Model information from XML file directly with the SMBIOS data returned in WMI queries like Win32_ComputerSystemProduct. Right now Dell provides some model information in the XML:

<Brand key=90 prefix=TABLET>
<Display lang=en><![CDATA[ Tablet ]]></Display>
   <Model systemID=0630>
      <Display lang=en><![CDATA[ 5830 ]]></Display>

But how to cross reference that with my Dell Venue 8 Pro:

PS C:\> Get-WMIObject Win32_ComputerSystemProduct

IdentifyingNumber : 123QWER
Name              : Venue 8 Pro 5830
Vendor            : DellInc.
Version           : Not Specified
Caption           : Computer System Product

I could try to do some sub-string matching, but I can’t seem to come up with the ideal pattern matching system, in addition, I want to support Johan’s Total-Control system, and if Dell doesn’t support the full Name “Venue 8 Pro” out of the “Venue 8 Pro 5830” I can’t construct the correct folder name.

Request: I would like Dell to supply the *FULL* SMBios name for each platform in the DriverPackCatalog.xml file.

Well, Dell went and updated the schema, but instead of adding full Model strings for the SupportedSystems, they modified the schema in ways that broke some other things in my scripts. My scripts have been hopelessly out of date and broken for some time. :^(

Updated Scripts

I updated the Install-DellWinPEDriverCatalog.ps1 file to match the new Dell Schema.

Fair warning, I don’t have enough Dell machines to do proper testing, but from what I can tell it was able to import WinPE and Some Latitude drivers into my test MDT environment.


(perhaps Dell can send me a new Precision Workstation so I can do proper testing ;^)

Updated Scripts:




Adding Help to your PowerShell Script

So, after working on your PowerShell script for a while, you figure it’s time to cleanup the comments and add some help so other people can use it.

PowerShell has an existing framework setup for getting help that most people already understand, it’s the “Get-Help” cmdlet, but most PowerShell scripters don’t realize that you can use “Get-Help” for your script, if formatted correctly.

The wrong way

First off, let’s discuss how not to do help, occasionally, I will see scripts that try to parse the arguments passed into the script:

if(($args.Count -gt 0) -and (
        ($args[0].ToLower() -eq "/?") -or 
        ($args[0].ToLower() -eq "/help") -or 
        ($args[0].ToLower() -eq "-h") -or 
        ($args[0].ToLower() -eq "--help") -or 
        ($args[0].ToLower() -eq "-help")))  {

    Write-Host(" - Get Some Info: GetInfo")
    Write-Host(" - Get Remote Info: GetInfo <COMPUTER NAME | IP>")
    Write-Host(" - Get Many Remote Info: GetInfo <COMPUTER NAME | IP> <COMPUTER NAME | IP> (Repeat as many times as needed")
    Write-Host(" - Show this help: GetInfo /? | /help | -h | --help")
} Else {
    if($args.Count -eq 0) {$args = ("localhost")}
    Foreach($computer in $args) {
        Write-Host "Do Something $Computer"

This is very much a C++/C#/Java way of parsing arguments, and doesn’t leverage built-in PowerShell functionality.


Powershell has built in functionality for self documenting help based on the comment block of your function or script:

#Requires -Version 3
#Requires -RunAsAdministrator 

Make a VHD File bootable
Converts a raw *.vhd file to a bootable *.vhd file
File path to a VHD File to be made active
.\Make-VHDBootable.ps1 -verbose -path C:\15\install.vhdx
Copyright Microsoft Corp, All Rights reserved.

       [string] $Path

$MountedVHD = Mount-VHD -Path $Path –Passthru
if ( $MountedVHD ) {
    $MountedVHD | Out-String | Write-Verbose
    $Drive = $MountedVHD | Get-Disk | Get-Partition | Get-Volume | Select-Object -ExpandProperty DriveLetter
    if ( Test-Path "$Drive`:\Windows\System32\BCDBoot.exe" ) {
        & "$Drive`:\Windows\System32\BCDBoot.exe" "$Drive`:\Windows" /s "$Drive`:" /F ALL | out-string |write-verbose
    Dismount-VHD -Path $Path

Note how I have two #requires strings at the top of the script, this is an easy way to enforce basic script requirements. Most of the scripts I write require Elevated Administrative privileges, and this is the quickest way to make sure this works.

There is a large comment block at the top of the script describing what the script does. See: about_comment_based_help

Also note that I try to match the Parameters for my function with any cmdlets I will pass the parameters to within the function, that way I can convert to Splatting down the road if beneficial.


We know how get-help works for most of our cmdlets, and it’s a great way to learn the details of what functions do. But did you know that get-help also works for scripts?

Just run get-help with your full script path as the parameter:

PS C:\Users\Keith\Desktop> get-help .\Make-VHDBootable.ps1

    Make a VHD File bootable
    C:\Users\Keith\Desktop\Make-VHDBootable.ps1 [-Path] <String> [<CommonParameters>]
    Converts a raw *.vhd file to a bootable *.vhd file

    To see the examples, type: "get-help C:\Users\Keith\Desktop\Make-VHDBootable.ps1 -examples".
    For more information, type: "get-help C:\Users\Keith\Desktop\Make-VHDBootable.ps1 -detailed".
    For technical information, type: "get-help C:\Users\Keith\Desktop\Make-VHDBootable.ps1 -full".

Find errors quickly in a SCCM or MDT log file.

I have an automated system to build out my Windows Images locally, 9 images in total using MDT 2013 U1 Beta Litetouch

  • Windows Server 2008 R2
  • Windows Server 2012 R2
  • Windows Server Technical Preview 2 (build 10074)
  • Windows 7 SP 1 – Both x86 and x64
  • Windows 8.1 Update 3 – Both x86 and x64
  • Windows 10 Build 10074 – Both x86 and x64

But I wondered today if everything was *really* going as well as I thought… The recommended way to check is to open the bdd.log file in cmtrace.exe and look for any Yellow or Red lines, warning and Errors!

SCCM and MDT use a special logging format so it’s pretty easy to search for errors using a script.

In powershell I can type in:

gci -recurse bdd.log | gc | Select-String "type=""(2|3)"""

I didn’t like the output, as it only returns the error line, without the file and line number, so I started to play around the script, and noticed that by removing the “get-content” command, Select-String did the right thing and opened the file in question

gci -recurse bdd.log | Select-String "type=""(2|3)"""

Yea! it worked, and found errors… :^)

Now I have to fix the errors. :^(

Driver Packages for Lenovo and HP

I’ve had several requests to extend my Dell Driver Pack Catalog Tool to Lenovo and HP.

I got as far as developing a powershell script to get a Lenovo driver manifest by web-scraping the support.lenovo.com web site:

#requires -version 3
#Requires -RunAsAdministrator


Write-verbose “Download Model List”
$AllModels = Invoke-WebRequest http://support.lenovo.com/en/documents/ht074984
$ModelPages = $AllModels.links.href |
   select-string -allmatches “(?:/docs/|/us/en/downloads/)(ds[0-9]+)” |
   ForEach-Object { $_.matches.groups[1].value } |
   Select-Object -Unique

Write-Debug “Done: Count[$($ModelPages.count)]”

$i = 0
$manifWrite-Verbose “Download each model page”
ForEach ( $Model in $ModelPages ) {
Write-Progress -Activity “Download $Model -PercentComplete ($i * 100 / $ModelPages.Count)
    Invoke-WebRequest http://support.lenovo.com/us/en/downloads/$Model |
        ForEach-Object {
            [PSCustomObject] @{
                 PackageID = $Model;
                 Title = $_.ParsedHTML.Title;
                 Download = ($_.links.href -match “.*exe”)

Write-Progress -Activity “Done” -Completed

The next step is to download and extract the *.exe package (with the /silent or /VerySilent switch), and then import into MDT Litetouch or SCCM

Unfortunately, I’m not very happy about the way the Model numbers are displayed/parsed by the web site and my script, and I would prefer something that is machine readable (XML file, for example). This would allow us to follow the Johan “Total Control” method of driver management.

If you know how to get the Model Numbers for Lenovo machines given the packages from the web site, let me know.





How to change uEFI boot order

I’ve been doing a lot of USB and PXE installations on uEFI physical hardware lately. Most of the machines I’ve used in the past have been Lenovo/Dell/HP machines that support F9 or F12 boot overrides, however my Surface test machines ( Surface Pro (1), Surface Pro 3, and Surface 3) don’t support the typical F9 or F12 overrides, instead you have to hold down the Vol-Up key while pressing power on.

Well sometimes I get distracted, and forget to press the button, or sometimes my machine is just slow.


Back in the BIOS days, it was easy for me to change the default boot order, for starters, I always set the Hard Disk to 1st priority, I never set PXE or USB to higher priority, that’s a security vulnerability. So on a BIOS machine, all you have to do is disable the active bit on the local hard disk system partition, and the BIOS will boot to the next item in the list.

Here is a script I wrote to do this:

@if not defined debug echo off

@echo  This script will SABOTAGE the main hard disk and reboot the machine.


if exist c:\minint rd /s /q c:\Minint
if exist d:\minint rd /s /q d:\Minint
if exist e:\minint rd /s /q e:\Minint
if exist c:\_SMSTaskSequence rd /s /q c:\_SMSTaskSequence

@echo List Disk
@echo Select Disk 0
@echo List Partition 
@echo Select Partition 1
@echo Inactive
@echo Select Partition 2
@echo Inactive
@echo Exit
) | diskpart.exe

WPEUtil.exe reboot
shutdown.exe -s -f -t 0

The script will try to inactivate two different partitions just to be sure, and I run two different commands to reboot, one for Full Windows, and one for WinPE.

Additionally, if you decide later on that you don’t want to wipe the machine and install a new OS, you can boot into WinPE, and re-activate the System partition, and you got your full OS back.


Well uEFI is a bit harder, and I finally think I’ve come across a way to disable booting from the system partition. The challenge is that the files on the System Partition are on a volume that does not have a drive letter, so it’s harder to gain access, but not totally impossible. Turns out that we can use the volume mount points to gain access.

On most of my test machines the System partition is on Disk 0 Partition 2, so the mount point is \\?\HardDiskVolume2


We just enumerate through all partitions till we find the correct one.

(Please don’t ask about accessing these \\?\HardDiskVolume2 mount points in Powershell, it’s hard).


@if not defined debug echo off 

@echo  This script will SABOTAGE the main hard disk and reboot the machine.


for /L %%i in ( 1,1,10 ) do (
  if exist \\?\HarddiskVolume%%i\efi\boot\bootx64.* (
    echo Found uEFI drive \\?\HarddiskVolume%%i
    rename \\?\HarddiskVolume%%i\efi\boot\*.efi *.bak
    rename \\?\HarddiskVolume%%i\efi\Microsoft\boot\*.efi *.bak
    WPEUtil.exe reboot
    shutdown.exe -s -f -t 0
echo No uEFI drive found

Be careful about USB drives that are mounted on the local system, the script should search and find the local volumes first, but just to be sure remove your USB stick.

Additionally, if you find that you want to keep your existing OS, just boot into WinPE, and rename all the *.bak files to *.efi.

More about MDT 2013 Update 1 Preview

Microsoft released the much anticipated MDT 2013 Update 1 Preview this week, See the announcement here.

Johan Arwidmark has already done an excellent analysis of the latest build, but I though I would pile on as well. :^)

So what issues am I tracking:

  1. Perhaps the biggest missing component is a new task sequence for the new Windows 10 “OS Upgrade”. There have been several blog posts on the subject, so I would have expected to see it by now.
  2. .\Templates\Server.xml has a fix for ImageBuild > 6 in Sysprep, but .\Templates\Client.xml is missing that fix.
  3. Still a lot of XP and Vista legacy code in LTIApply.wsf and LTISysprep.wsf.
  4. Readers of this blog may recall my fix for LTIApply.wsf and split WIM’s. The solution Microsoft has chosen to address Split Wim’s is very similar ;^)
  5. Only minor fixes to ZTIWindowsUpdate.wsf. I have several other updates on my blog.
  6. I have a minor quibble about the new Function GetMajorMinorVersion. It’s not a function, it’s a Sub since it does not return any values. I would have preferred that the function return the Major and Minor OS version as a Float.
  7. I have 20 or so bugs on Connect, and so far I can see only the split wim fix in the Preview.
  8. Although MSFT has fixed several bugs related to the version number jump “6.4” > “10.0” they didn’t fix any build number problems “6000” > “10007”.
  9. I can see that Microsoft added some entries for ServerManager.xml, but they copied the Windows 10 entries from above (see how Roles ID 12 is repeated twice), I wrote a script a while back to programmatically generate the ServerManager.xml file from the OS itself. You can see my changes on MDTEx.

I have been making my own modifications to MDT 2013 Update 1 Preview to match my own build environment, and rather than place the files up on OneDrive, or other share, I have decided it’s time to put them in a proper Version Control System for tracking. The placeholder site for now is at: http://MDTEx.CodePlex.com. These changes are strictly unsupported, and provided only as a starting point for discussions on MDT script changes. :^)

Updated Install-DellDriverCatalog.ps1

I updated the Install-DellDriverCatalog.ps1 up on my OneDrive account, since starting tomorrow I may not be able to update the file any more (Competition and all :^)…

Thanks to Mike Sobol for finding the error and reporting it,

Turns out that Dell changed the Schema of the catalog, forcing an update for some of the new Dell models like the E7250.

MDT ZTIDrivers.wsf will try to do a good job matching the driver package downloaded with the models identified during install, but sometimes we have a hard time matching the drivers in the catalog with the make and model of the machine because Dell doesn’t give an exact match to what is found in the BIOS/Firmware, instead we have to do an intelligent job of matching the values.

For the new models Dell lists the Model as:

      <Brand key="4" prefix="LAT">
        <Display lang="en"><![CDATA[ Latitude ]]></Display>
        <Model systemID="062D">
          <Display lang="en"><![CDATA[ E7250/7250 ]]></Display>
      <Brand key="4" prefix="LAT">
        <Display lang="en"><![CDATA[ Latitude ]]></Display>
        <Model systemID="0648">
          <Display lang="en"><![CDATA[ E7250/7250 ]]></Display>

As you can see the model is listed with a “/” in the name, which is different than the other machines.

So I changed the script to filter out the BIOS values.

I just wish Dell would be consistent with their naming structure, or at least list the model value as it appears in the BIOS.

Download files from: https://wordpress.com/post/keithga.wordpress.com/588


Updated – PS2Wiz – Addition of Out-GridView support

I updated the PS2Wiz tool on CodePlex: http://PS2Wiz.CodePlex.com.


I’ve been using the PowerShell cmdlet Out-GridView for selecting and managing datasets in a graphical manner, and wanted to see if I could extend that functionality to the PS2Wiz tool. Additionally, I was a bit disappointed in the lack of ListBox support in the existing PS2Wiz implementation. The only features to allow a user to select an item from a list was the PromptForChoice() function as a collection of Radio Buttons.

What I ended up with is an override replacement of the Out-GridView cmdlet within PS2Wiz, meaning that when you call Out-GridView in your script, rather than calling the native PowerShell Out-GridView cmdlet with it’s own window, PS2Wiz redirects the command to the PS2Wiz version which displays the content in the Local Wizard Window.

For example if you were to create a PowerShell script for PS2Wiz that ran the command:

get-childitem c:\ | out-gridview

It would show in the wizard like:

You can also output an array of strings to Out-GridView and have it display as a list

type c:\windows\win.ini |out-gridview


The internal PS2Wiz version of Out-GridView supports all of the standard Out-GridView Parameters like -Wait -PassThru and -OutputMode. It’s a pretty good replacement for the internal Out-GridView cmdlet.

One exception is that the PS2Wiz version of Out-GridView supports passing in arrays for the -InputObject parameter. So the following command will list items in PS2Wiz, but not in regular PowerShell.exe

out-gridview -InputObject (get-Process |select-Object ID,Name)


Additionally, I added a treeview control for MDT Objects. This allows for some future MDT management scripts.

Import-Module "C:\Program Files\Microsoft Deployment Toolkit\bin\MicrosoftDeploymentToolkit.psd1"
New-PSDrive -Name "DS001" -PSProvider MDTProvider -Root "C:\DeploymentShare" | out-null
Select-MDTObject -OutputMode Multiple -RootNode "ds001:\Applications"


This allows me to develop some tools that manage MDT Shares, selecting Applications, Operating Systems, Drivers, Packages, TaskSequences, and more..

Could be useful. 🙂

In-depth: PowerShell Out-GridView cmdlet

Been digging into the internals of the PowerShell CmdLet Out-GridView, Almost reverse engineering how the feature works.


Out-GridView is essentially a wrapper around the Microsoft .NET DataGridView Windows Form Control. As long as we can translate the PowerShell data into a form that the DataGridView can display, DataGridView will do most of the work for us.
DataGridView is quite powerfull, able to handle display for a wide range of data types, string, arrays, and can natively handle XML data types.

Display Modes and Inputs

Out-GridView has several display modes:

  • Non-Blocking ( no wait *Default* ) – Display the data in the Out-GridView window and immediately return, allows the script to continue running.
  • Blocking ( -Wait ) – Display the data in the Out-GridView window and wait until the
  • Single ( -OutputMode Single ) – Display the data in the Out-GridView window. User can select one line, and the entry is returned as the object in the pipeline (equivalent to the -PassThru switch)
  • Multiple ( -OutputMode Multiple) – Display the data in the Out-GridView window. User can select one or more lines, and the selected entries are returned as results in the pipeline.

In addition to the mode, there is also an option to change the title.


Passing data into Out-GridView is very easy, just pass in via the pipeline:

get-process |out-gridview

The -InputObject parameter accepts pipeline input, now you would think that we could also just pass the output as a parameter, like this:

out-gridview -inputobject (get-process)

However, Out-GridView doesn’t like that, it treats any data passed in as a parameter as a single object and displays as a single line in the Out-GridView window. Which is confirmed by the online help for Out-GridView:

Accepts input for Out-GridView.

When you use the InputObject parameter to send a collection (more than one) 
of objects to Out-GridView, Out-GridView treats the collection as one 
collection object, and it displays one row that represents the collection.  
To display the each object in the collection, use a pipeline operator (|) 
to send objects to Out-GridView.

There is one exception to this rule, is that if you pass in a C# IDictionary collection (PowerShell HashTable), as a parameter, Out-GridView will parse that input as multiple objects:

$g = @{} ; get-process | %{ $g.Add($_.ID,$_.Name) } ; out-gridview -inputobject $g

I personally don’t like the way Out-GridView does this, and if I had to change the default behavior I force the enumeration of arrays.


Additionally, PowerShell has some extensive support for advanced formatting that the DataGridView does not understand, so we need to help DataGridView understand which fields to use in the display. Out-GridView appears to display the same columns and data as the “Format-Table” shows, so it’s a matter of extracting out that data.

If we were to run the command “Get-ChildItem -file” and view the output using the “Get-Member” cmdlet we can see that it outputs the Type “System.IO.FileInfo”

PS C:\> get-childitem -file | gm

TypeName: System.IO.FileInfo

Name MemberType Definition
---- ---------- ----------
Mode CodeProperty System.String Mode{get=Mode;}
AppendText Method System.IO.StreamWriter AppendText()

We can then use the "get-FormatData" cmdlet to get a list of different formatting types associated with the selected type:

PS C:\> (get-formatdata System.Diagnostics.Process).formatviewDefinition

Name Control
---- -------
children TableControl
children ListControl
children WideControl

If we can dig further into the formatting information to get a list of Columns and formatting data for each row.

However, it's important to note that sometimes the Row data can contain PowerShell Code, so we may need to call *Back* to our PowerShell environment to parse and get the correct data.

PS C:\> $(get-formatdata System.Diagnostics.Process).formatviewDefinition. `

[long]($_.NPM / 1024)
[long]($_.PM / 1024)
[long]($_.WS / 1024)
[long]($_.VM / 1048576)

if ($_.CPU -ne $())


Now we have the information to display PowerShell data in a structured graphical way!


New Tool – PS2Wiz – Run PowerShell scripts in a Wizard Host! http://ps2wiz.codeplex.com/

Posted my first project to CodePlex: PowerShell Host Wizard at http://ps2wiz.codeplex.com

It’s a way to distribute your PowerShell Scripts as an Executable (*.exe) with a Wizard User Interface.


It’s intended for those PowerShell developers who may be getting pressure to create fancy User Interfaces, but don’t want to get into the quagmire of developing custom Forms just as a front end to the PowerShell script that does the real work. PS2Wiz will automatically hook into the PowerShell system and construct the User Interface elements automatically when requested by the script.

* Write-Host – will become a label.
* Read-Host – will become a TextBox.
* Get-Credential – will display two TextBoxes for credentials.
* Copy with -Confirm – will display a radio button to confirm.
* Call a function with Mandatory Parameters – Those get auto generated.
* and more…


I created a video to demonstrate how it works.



Special Thanks to ikarstein for the excellent work on PS2Exe. Got me inspired to release it to CodePlex.
(And to @mikael_nystrom for pointing me to PS2Exe a few weeks ago via twitter)