Mick's IT Blogs

Mick's IT Blogs

31 May 2016

PowerShell: Set Windows Features with Verification

Posted By: Mick Pletcher - 2:09 PM














I am in the beginning stages of creating a Windows 10 build. One of the first things I needed to do was to install and set the Windows 10 features. Before, I used a batch script that executed DISM to set each feature. I know there is the Install-WindowsFeatures cmdlet, but I also wanted to incorporate verification and other features into a single script. With the help of Sapien's PowerShell Studio, this script was a breeze to write.

This script allows you to set windows features while also verifying each feature was set correctly by querying the feature for the status. It then outputs the feature name and status to the display. I have also included the option to run a report of all available features and their state. Here are the four features the script provides:

  1. Set an individual feature via command line:
    powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -Feature 'RSATClient-Features' -Setting 'disable'
  2. Set multiple features by reading a text file located in the same directory as the script. You can name the text file any name you want. The format for the file is: RSATClient,enable for example. Here is the command line:
    powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -FeaturesFile 'FeaturesList.txt'

  3. Hard code a feature setting at the bottom of the script:
    Set-WindowsFeature -Name 'RSATClient-Features' -State 'disable'
  4. Display a list of windows features:
    powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -ListFeatures $true


You will need to use the -command when executing this at the command line instead of -file. This is because the -ListFeatures is a boolean value. I have also included code that identifies an error 50 and returns a status that you must include the parent feature before activating the specified feature. I have also made the additional command line window be minimized when running the DISM.exe. 

You can download the script from here.
1:  <#  
2:       .SYNOPSIS  
3:            Process Windows Features  
4:         
5:       .DESCRIPTION  
6:            This script can return a list of online windows features and/or set specific windows features.  
7:         
8:       .PARAMETER ListFeatures  
9:            Return a list of all Windows Features  
10:         
11:       .PARAMETER Feature  
12:            A description of the Feature parameter.  
13:         
14:       .PARAMETER Setting  
15:            A description of the Setting parameter.  
16:         
17:       .PARAMETER FeaturesFile  
18:            Name of the features file that contains a list of features with their corresponding settings for this script to process through. The files resides in the same directory as this script.  
19:         
20:       .EXAMPLE  
21:            Return a list of all available online Windows Features  
22:            powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -ListFeatures $true  
23:              
24:            Set one Windows Feature from the command line  
25:            powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -Feature 'RSATClient-Features' -Setting 'disable'  
26:              
27:            Set multiple features by reading contents of a text file  
28:            powershell.exe -executionpolicy bypass -command WindowsFeatures.ps1 -FeaturesFile 'FeaturesList.txt'  
29:         
30:       .NOTES  
31:            You must use -command instead of -file in the command line because of the use of boolean parameters  
32:    
33:            An error code 50 means you are trying to enable a feature in which the required parent feature is disabled  
34:              
35:            I have also included two commented out lines at the bottom of the script as examples if you want to hardcode the features within the script.  
36:            ===========================================================================  
37:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.122  
38:            Created on:       5/27/2016 2:46 PM  
39:            Created by:       Mick Pletcher  
40:            Organization:  
41:            Filename:         WindowsFeatures.ps1  
42:            ===========================================================================  
43:  #>  
44:  [CmdletBinding()]  
45:  param  
46:  (  
47:            [boolean]$ListFeatures = $false,  
48:            [string]$Feature,  
49:            [ValidateSet('enable', 'disable')][string]$Setting,  
50:            [String]$FeaturesFile  
51:  )  
52:    
53:  function Confirm-Feature {  
54:  <#  
55:       .SYNOPSIS  
56:            Confirm the feature setting  
57:         
58:       .DESCRIPTION  
59:            Confirm the desired change took place for a feature  
60:         
61:       .PARAMETER FeatureName  
62:            Name of the feature  
63:         
64:       .PARAMETER FeatureState  
65:            Desired state of the feature  
66:         
67:       .EXAMPLE  
68:            PS C:\> Confirm-Feature  
69:         
70:       .NOTES  
71:            Additional information about the function.  
72:  #>  
73:         
74:       [CmdletBinding()][OutputType([boolean])]  
75:       param  
76:       (  
77:                 [ValidateNotNull()][string]$FeatureName,  
78:                 [ValidateSet('Enable', 'Disable')][string]$FeatureState  
79:       )  
80:         
81:       $WindowsFeatures = Get-WindowsFeaturesList  
82:       $WindowsFeature = $WindowsFeatures | Where-Object { $_.Name -eq $FeatureName }  
83:       switch ($FeatureState) {  
84:            'Enable' {  
85:                 If (($WindowsFeature.State -eq 'Enabled') -or ($WindowsFeature.State -eq 'Enable Pending')) {  
86:                      Return $true  
87:                 } else {  
88:                      Return $false  
89:                 }  
90:            }  
91:            'Disable' {  
92:                 If (($WindowsFeature.State -eq 'Disabled') -or ($WindowsFeature.State -eq 'Disable Pending')) {  
93:                      Return $true  
94:                 } else {  
95:                      Return $false  
96:                 }  
97:            }  
98:            default {  
99:                 Return $false  
100:            }  
101:       }  
102:         
103:  }  
104:    
105:  function Get-WindowsFeaturesList {  
106:  <#  
107:       .SYNOPSIS  
108:            List Windows Features  
109:         
110:       .DESCRIPTION  
111:            This will list all available online windows features  
112:         
113:       .NOTES  
114:            Additional information about the function.  
115:  #>  
116:         
117:       [CmdletBinding()]  
118:       param ()  
119:         
120:       $Temp = dism /online /get-features  
121:       $Temp = $Temp | Where-Object { ($_ -like '*Feature Name*') -or ($_ -like '*State*') }  
122:       $i = 0  
123:       $Features = @()  
124:       Do {  
125:            $FeatureName = $Temp[$i]  
126:            $FeatureName = $FeatureName.Split(':')  
127:            $FeatureName = $FeatureName[1].Trim()  
128:            $i++  
129:            $FeatureState = $Temp[$i]  
130:            $FeatureState = $FeatureState.Split(':')  
131:            $FeatureState = $FeatureState[1].Trim()  
132:            $Feature = New-Object PSObject  
133:            $Feature | Add-Member noteproperty Name $FeatureName  
134:            $Feature | Add-Member noteproperty State $FeatureState  
135:            $Features += $Feature  
136:            $i++  
137:       } while ($i -lt $Temp.Count)  
138:       $Features = $Features | Sort-Object Name  
139:       Return $Features  
140:  }  
141:    
142:  function Set-WindowsFeature {  
143:  <#  
144:       .SYNOPSIS  
145:            Configure a Windows Feature  
146:         
147:       .DESCRIPTION  
148:            Enable or disable a windows feature  
149:         
150:       .PARAMETER Name  
151:            Name of the windows feature  
152:         
153:       .PARAMETER State  
154:            Enable or disable windows feature  
155:         
156:       .NOTES  
157:            Additional information about the function.  
158:  #>  
159:         
160:       [CmdletBinding()]  
161:       param  
162:       (  
163:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$Name,  
164:                 [Parameter(Mandatory = $true)][ValidateSet('enable', 'disable')][string]$State  
165:       )  
166:         
167:       $EXE = $env:windir + "\system32\dism.exe"  
168:       Write-Host $Name"....." -NoNewline  
169:       If ($State -eq "enable") {  
170:            $Parameters = "/online /enable-feature /norestart /featurename:" + $Name  
171:       } else {  
172:            $Parameters = "/online /disable-feature /norestart /featurename:" + $Name  
173:       }  
174:       $ErrCode = (Start-Process -FilePath $EXE -ArgumentList $Parameters -Wait -PassThru -WindowStyle Minimized).ExitCode  
175:       If ($ErrCode -eq 0) {  
176:            $FeatureChange = Confirm-Feature -FeatureName $Name -FeatureState $State  
177:            If ($FeatureChange -eq $true) {  
178:                 If ($State -eq 'Enable') {  
179:                      Write-Host "Enabled" -ForegroundColor Yellow  
180:                 } else {  
181:                      Write-Host "Disabled" -ForegroundColor Yellow  
182:                 }  
183:            } else {  
184:                 Write-Host "Failed" -ForegroundColor Red  
185:            }  
186:       } elseif ($ErrCode -eq 3010) {  
187:            $FeatureChange = Confirm-Feature -FeatureName $Name -FeatureState $State  
188:            If ($FeatureChange -eq $true) {  
189:                 If ($State -eq 'Enable') {  
190:                      Write-Host "Enabled & Pending Reboot" -ForegroundColor Yellow  
191:                 } else {  
192:                      Write-Host "Disabled & Pending Reboot" -ForegroundColor Yellow  
193:                 }  
194:            } else {  
195:                 Write-Host "Failed" -ForegroundColor Red  
196:            }  
197:       } else {  
198:            If ($ErrCode -eq 50) {  
199:                 Write-Host "Failed. Parent feature needs to be enabled first." -ForegroundColor Red  
200:            } else {  
201:                 Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
202:            }  
203:       }  
204:  }  
205:    
206:  function Set-FeaturesFromFile {  
207:  <#  
208:       .SYNOPSIS  
209:            Set multiple features from a text file  
210:         
211:       .DESCRIPTION  
212:            This function reads the comma separated features and values from a text file and executes each feature.  
213:         
214:       .NOTES  
215:            Additional information about the function.  
216:  #>  
217:         
218:       [CmdletBinding()]  
219:       param ()  
220:         
221:       $RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + '\'  
222:       $FeaturesFile = $RelativePath + $FeaturesFile  
223:       If ((Test-Path $FeaturesFile) -eq $true) {  
224:            $FeaturesFile = Get-Content $FeaturesFile  
225:            foreach ($Item in $FeaturesFile) {  
226:                 $Item = $Item.split(',')  
227:                 Set-WindowsFeature -Name $Item[0] -State $Item[1]  
228:            }  
229:       }  
230:  }  
231:    
232:  Clear-Host  
233:  If ($ListFeatures -eq $true) {  
234:       $WindowsFeatures = Get-WindowsFeaturesList  
235:       $WindowsFeatures  
236:  }  
237:  If ($FeaturesFile -ne '') {  
238:       Set-FeaturesFromFile  
239:  }  
240:  If ($Feature -ne '') {  
241:       Set-WindowsFeature -Name $Feature -State $Setting  
242:  }  
243:    

26 May 2016

Windows 10: Windows could not parse or process the unattend answer file for pass [specialize]

Posted By: Mick Pletcher - 2:50 PM
















I am in the beginning stages of working on upgrading the firm I work at to Windows 10. Using MDT, The OS would inject drivers and lay down with no issues. Upon the first reboot, it would pop up the following error message:


The first thing I did was to look at the Unattend.xml file by opening the task sequence properties--->OS Info tab. When I looked under the Specialize--->Microsoft-Windows-Shell-Setup, everything looked normal, except for one thing. The product code was showing the code for Windows 7 and I had entered the code during the task sequence setup phase. I proceeded to change to the correct Windows 10 product code. The same error came up again. At that point, I cleared every field within the Specialize--->Microsoft-Windows-Shell-Setup by right-clicking and selecting Revert Change. The same issue occurred again. I hit Shift-F10 to pull up a command line. I got the c:\windows\panther files copied to a thumb drive and looked at the logs on my other machine. I saw the following errors in the setupact.log file:

At this point, I decided to completely delete the task sequence and start all over. The issue persisted. Finally, I deleted the Microsoft-Windows-Shell-Setup from the Specialize component. I re-imaged the machine and it now goes through the first bootup/setup phase with no problems. I already use PowerShell to set the product code after the image is laid down, so that was not a big deal.

After deleting the Microsoft-Windows-Shell-Setup, the image went through successfully, but I ran into other issues. The system was not joined to the domain and was not named for the computer name I set.

I added the amd64_Microsoft-Windows-Shell-Setup_neutral back to the 4 specialize component. I then right-clicked on the ComputerName setting and selected Write Empty String as shown below.


I proceeded to reimage the machine and it imaged successfully, joined the domain, and had the correct computer name.

18 May 2016

Configure PowerShell Settings

Posted By: Mick Pletcher - 12:50 PM










Recently, we needed to start building select machines off of our domain for special projects. This meant that group policies would not be applied. I use GPOs to set PowerShell settings on all of the machines. With these machines no longer getting GPOs applied, PowerShell scripts would no longer execute correctly because some of my scripts also use a module I push via GPO and the execution policy was not updated. This lead me to write, with the help and ease of Sapien's PowerShell Studio, the script below that will set the execution policy, configure the RunAs Administrator, configure additional paths for PowerShell modules, and copies PowerShell modules over. This gets executed after the first windows updates are applied in the task sequencing. If the script is executed manually, there is an output screen that shows if each setting is a success or failure.

To use this script, you will need to update/verify lines 212, 217, 224, and 225. Of course, if you don't want all of those things to change, you can comment some of them out.

You can download the script from here.


1:  <#  
2:       .SYNOPSIS  
3:            Configure PowerShell  
4:         
5:       .DESCRIPTION  
6:            Configure PowerShell execution policy and install PowerShell modules.  
7:         
8:       .DESCRIPTION  
9:            A description of the file.  
10:         
11:       .PARAMETER PSConsoleTitle  
12:            Title of the PowerShell Console  
13:         
14:       .EXAMPLE  
15:            powershell.exe -executionpolicy bypass -file ConfigurePowerShell.ps1  
16:         
17:       .NOTES  
18:            ===========================================================================  
19:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.122  
20:            Created on:       5/18/2016 12:12 PM  
21:            Created by:       Mick Pletcher  
22:            Organization:  
23:            Filename:         ConfigurePowerShell.ps1  
24:            ===========================================================================  
25:  #>  
26:  [CmdletBinding()]  
27:  param  
28:  (  
29:            [string]$PSConsoleTitle = 'PowerShell Configuration'  
30:  )  
31:    
32:  function Set-ConsoleTitle {  
33:  <#  
34:       .SYNOPSIS  
35:            Console Title  
36:         
37:       .DESCRIPTION  
38:            Sets the title of the PowerShell Console  
39:         
40:       .PARAMETER ConsoleTitle  
41:            Title of the PowerShell Console  
42:         
43:       .NOTES  
44:            Additional information about the function.  
45:  #>  
46:         
47:       [CmdletBinding()]  
48:       param  
49:       (  
50:                 [Parameter(Mandatory = $true)][String]$ConsoleTitle  
51:       )  
52:         
53:       $host.ui.RawUI.WindowTitle = $ConsoleTitle  
54:  }  
55:    
56:  function Get-RelativePath {  
57:  <#  
58:       .SYNOPSIS  
59:            Get the relative path  
60:         
61:       .DESCRIPTION  
62:            Returns the location of the currently running PowerShell script  
63:         
64:       .NOTES  
65:            Additional information about the function.  
66:  #>  
67:         
68:       [CmdletBinding()][OutputType([string])]  
69:       param ()  
70:         
71:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
72:       Return $Path  
73:  }  
74:    
75:  function Set-RegistryKeyValue {  
76:  <#  
77:       .SYNOPSIS  
78:            Test if a registry value exists  
79:         
80:       .DESCRIPTION  
81:            This tests to see if a registry value exists by using the get-itemproperty and therefore returning a boolean value if the cmdlet executes successfully.  
82:         
83:       .PARAMETER RegKeyName  
84:            Registry key name  
85:         
86:       .PARAMETER RegKeyValue  
87:            Value within the registry key  
88:         
89:       .PARAMETER RegKeyData  
90:            The data pertaining to the registry key value  
91:         
92:       .PARAMETER DisplayName  
93:            Name to be used to display on the status window  
94:         
95:  #>  
96:         
97:       [CmdletBinding()]  
98:       param  
99:       (  
100:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$RegKeyName,  
101:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$RegKeyValue,  
102:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()]$RegKeyData,  
103:                 [string]$DisplayName = $null  
104:       )  
105:         
106:       If ($DisplayName -ne $null) {  
107:            Write-Host "Setting"$DisplayName"....." -NoNewline  
108:       }  
109:       $NoOutput = New-PSDrive -Name HKCR -PSProvider Registry -Root HKEY_CLASSES_ROOT  
110:       $Key = Get-Item -LiteralPath $RegKeyName -ErrorAction SilentlyContinue  
111:       If ($Key -ne $null) {  
112:            If ($RegKeyValue -eq '(Default)') {  
113:                 $Value = Get-ItemProperty $RegKey '(Default)' | Select-Object -ExpandProperty '(Default)'  
114:            } else {  
115:                 $Value = $Key.GetValue($RegKeyValue, $null)  
116:            }  
117:            If ($Value -ne $RegKeyData) {  
118:                 Set-ItemProperty -Path $RegKeyName -Name $RegKeyValue -Value $RegKeyData -Force  
119:            }  
120:              
121:       } else {  
122:            $NoOutput = New-Item -Path $RegKeyName -Force  
123:            $NoOutput = New-ItemProperty -Path $RegKeyName -Name $RegKeyValue -Value $RegKeyData -Force  
124:       }  
125:       If ($RegKeyValue -eq '(Default)') {  
126:            $Value = Get-ItemProperty $RegKey '(Default)' | Select-Object -ExpandProperty '(Default)'  
127:       } else {  
128:            $Value = $Key.GetValue($RegKeyValue, $null)  
129:       }  
130:       If ($DisplayName -ne $null) {  
131:            If ($Value -eq $RegKeyData) {  
132:                 Write-Host "Success" -ForegroundColor Yellow  
133:            } else {  
134:                 Write-Host "Failed" -ForegroundColor Red  
135:                 Write-Host $Value  
136:                 Write-Host $RegKeyData  
137:            }  
138:       }  
139:  }  
140:    
141:  function Copy-Files {  
142:  <#  
143:       .SYNOPSIS  
144:            Copy-Files  
145:         
146:       .DESCRIPTION  
147:            This will copy specified file(s)  
148:         
149:       .PARAMETER SourceDirectory  
150:            Directory containing the source file(s)  
151:         
152:       .PARAMETER DestinationDirectory  
153:            Directory where the source file(s) will be copied to  
154:         
155:       .PARAMETER FileFilter  
156:            Either a specific filename or a wildcard specifying what to copy  
157:         
158:       .EXAMPLE  
159:            Copy-Files -SourceDirectory 'c:\windows' -DestinationDirectory 'd:\windows' -FileFilter '*.exe'  
160:            Copy-Files -SourceDirectory 'c:\windows' -DestinationDirectory 'd:\windows' -FileFilter 'INSTALL.LOG'  
161:         
162:       .NOTES  
163:            Additional information about the function.  
164:  #>  
165:         
166:       [CmdletBinding()]  
167:       param  
168:       (  
169:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$SourceDirectory,  
170:                 [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][String]$DestinationDirectory,  
171:                 [ValidateNotNullOrEmpty()][String]$FileFilter  
172:       )  
173:         
174:       $Dest = $DestinationDirectory  
175:       If ((Test-Path $DestinationDirectory) -eq $false) {  
176:            $NoOutput = New-Item -Path $DestinationDirectory -ItemType Directory -Force  
177:       }  
178:       $Files = Get-ChildItem $SourceDirectory -Filter $FileFilter  
179:       If ($Files.Count -eq $null) {  
180:            Write-Host "Copy"$Files.Name"....." -NoNewline  
181:            Copy-Item $Files.FullName -Destination $Dest -Force  
182:            $Test = $Dest + "\" + $Files.Name  
183:            If (Test-Path $Test) {  
184:                 Write-Host "Success" -ForegroundColor Yellow  
185:            } else {  
186:                 Write-Host "Failed" -ForegroundColor Red  
187:            }  
188:       } else {  
189:            For ($i = 0; $i -lt $Files.Count; $i++) {  
190:                 $File = $Files[$i].FullName  
191:                 Write-Host "Copy"$Files[$i].Name"....." -NoNewline  
192:                 Copy-Item $File -Destination $Dest -Force  
193:                 $Test = $Dest + "\" + $Files[$i].Name  
194:                 If (Test-Path $Test) {  
195:                      Write-Host "Success" -ForegroundColor Yellow  
196:                 } else {  
197:                      Write-Host "Failed" -ForegroundColor Red  
198:                 }  
199:            }  
200:       }  
201:  }  
202:    
203:  Clear-Host  
204:  #Set the title of the PowerShell console  
205:  Set-ConsoleTitle -ConsoleTitle $PSConsoleTitle  
206:    
207:  #Define the relative path   
208:  $RelativePath = Get-RelativePath  
209:    
210:  #Configure additional paths for PowerShell modules  
211:  $RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment'  
212:  $RegValue = $env:SystemRoot + '\system32\WindowsPowerShell\v1.0\Modules\;' + $env:ProgramFiles + '\windowspowershell\modules'  
213:  Set-RegistryKeyValue -DisplayName "PSModulePath" -RegKeyName $RegKey -RegKeyValue 'PSModulePath' -RegKeyData $RegValue  
214:    
215:  #Set the PowerShell execution policy  
216:  $RegKey = 'HKLM:\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell'  
217:  Set-RegistryKeyValue -DisplayName "ExecutionPolicy" -RegKeyName $RegKey -RegKeyValue 'ExecutionPolicy' -RegKeyData 'RemoteSigned'  
218:    
219:  #Configure PowerShell RunAs Administrator  
220:  $RegKey = 'HKCR:\Microsoft.PowerShellScript.1\Shell\runas\command'  
221:  Set-RegistryKeyValue -DisplayName "RunAs Administrator" -RegKeyName $RegKey -RegKeyValue '(Default)' -RegKeyData '"c:\windows\system32\windowspowershell\v1.0\powershell.exe" -noexit "%1"'  
222:    
223:  #Copy PowerShell Modules  
224:  $ModuleFolder = $env:ProgramFiles + "\WindowsPowerShell\Modules\Deployment"  
225:  Copy-Files -SourceDirectory $RelativePath -DestinationDirectory $ModuleFolder -FileFilter "Deployment.psm1"  
226:    

03 May 2016

Install Dell Command Update and Flash BIOS in WinPE

Posted By: Mick Pletcher - 12:44 PM














I have wanted to get the process of flashing the BIOS into the WinPE environment for quite a while. With the help of Sapien's PowerShell Studio, I finally wrote the script to make this possible. The purpose is that it can flash it much quicker in the WinPE environment and it also gives two opportunities for he BIOS to be flashed during the build process. On some Dell machines, if the BIOS version is too far behind, there is an intermediary BIOS update that must be applied before the latest update can be. This gives the build the opportunity to get past that intermediary and apply the latest update before the build is completed.

To use this script, you will need to first install the Dell Command | Update on a system. If you are using the x86 version of WinPE, then install it on an x86 PC. The same on an x64 machine. Copy all of the contents of the %PROGRAMFILES%\Dell\CommandUpdate folder to a folder on a network share. Next, copy the msi.dll file from the %WINDIR%\System32 folder to the same folder share.

I created a task sequence folder and then put three task sequences underneath it.

Now it's time to create the Run Command Line task sequence. The first is to map to the folder share where you copied the CommandUpdate folder to. I used T: as my drive letter and used the following command: net use t: \\<NetworkShareFolder> /USER:<domain>\<username> <Password>. This is not recorded in any of the logs, so you don't risk the password being seen by unwanted eyes so long as they cannot access the task sequence.

The second task sequence is to install and update the Dell BIOS. This is also a Run Command Line sequence. The command line I use is: powershell.exe -executionpolicy bypass -file t:\install.ps1.

The final task sequence is unmapping the network drive via a Run Command Line sequence. The command line for this is net use t: /delete

I have also included in the script the ability to apply an XML file to the dcu-cli.exe. The XML file specifies what driver types to apply to the system. It looks for the XML file in the same directory as the PowerShell script. I found that if I execute the dcu-cli.exe with no switches, it does try to install one driver update to the WinPE environment, but it fails and continues to install the BIOS update with no issues.

Here is a video of the tool in action during the initial phase of the build process:


You can download the script from here.



UpdateBIOSWinPE.ps1

1:  <#  
2:       .SYNOPSIS  
3:            Install Dell Command | Update and Update the BIOS  
4:         
5:       .DESCRIPTION  
6:            Copy over the Dell Command | Update and install the BIOS  
7:         
8:       .PARAMETER Source  
9:            Source folder containing the Dell Command | Update files  
10:         
11:       .PARAMETER Destination  
12:            Location to copy the Dell Command | Update files to  
13:         
14:       .PARAMETER XMLFile  
15:            XML file that limits the Dell Command | Update to only scan for a BIOS update  
16:         
17:       .NOTES  
18:            ===========================================================================  
19:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.120  
20:            Created on:       4/27/2016 10:17 AM  
21:            Created by:       Mick Pletcher  
22:            Organization:  
23:            Filename:         UpdateBIOSWinPE.ps1  
24:            ===========================================================================  
25:  #>  
26:  [CmdletBinding()]  
27:  param  
28:  (  
29:            [string]$Source = 't:\',  
30:            [string]$Destination = 'x:\DCU',  
31:            [string]$XMLFile  
32:  )  
33:    
34:  function Copy-Folder {  
35:  <#  
36:       .SYNOPSIS  
37:            Copy Folder  
38:         
39:       .DESCRIPTION  
40:            Copy folder to destination  
41:         
42:       .PARAMETER SourceFolder  
43:            Folder to copy contents from  
44:         
45:       .PARAMETER DestinationFolder  
46:            Folder to copy contents to  
47:         
48:       .PARAMETER Subfolders  
49:            Include all subfolders  
50:         
51:       .PARAMETER Mirror  
52:            Mirror the destination folder with the source folder. Contents that exist in the destination folder, but not in the source folder, will be deleted.  
53:         
54:       .EXAMPLE  
55:            PS C:\> Copy-Folder -SourceFolder 'Value1' -DestinationFolder 'Value2'  
56:         
57:       .NOTES  
58:            Additional information about the function.  
59:  #>  
60:         
61:       [CmdletBinding()]  
62:       param  
63:       (  
64:                 [string]$SourceFolder,  
65:                 [string]$DestinationFolder,  
66:                 [ValidateSet($true, $false)][boolean]$Subfolders = $false,  
67:                 [ValidateSet($true, $false)][boolean]$Mirror = $false  
68:       )  
69:         
70:       $Executable = $env:windir + "\system32\Robocopy.exe"  
71:       $Switches = $SourceFolder + [char]32 + $DestinationFolder + [char]32 + "/eta"  
72:       If ($Subfolders -eq $true) {  
73:            $Switches = $Switches + [char]32 + "/e"  
74:       }  
75:       If ($Mirror -eq $true) {  
76:            $Switches = $Switches + [char]32 + "/mir"  
77:       }  
78:       Write-Host "Copying "$SourceFolder"....." -NoNewline  
79:       $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode  
80:       If (($ErrCode -eq 0) -or ($ErrCode -eq 1)) {  
81:            Write-Host "Success" -ForegroundColor Yellow  
82:       } else {  
83:            Write-Host "Failed with error code"$ErrCode -ForegroundColor Red  
84:       }  
85:  }  
86:    
87:  function Update-BIOS {  
88:  <#  
89:       .SYNOPSIS  
90:            Update to the latest BIOS Version  
91:         
92:       .DESCRIPTION  
93:            Execute the DCU-CLI.exe to query Dell for the latest BIOS version  
94:         
95:       .NOTES  
96:            Additional information about the function.  
97:  #>  
98:         
99:       [CmdletBinding()]  
100:       param ()  
101:         
102:       $Executable = $Destination + "\dcu-cli.exe"  
103:       If ($XMLFile -eq "") {  
104:            $Switches = " "  
105:       } else {  
106:            $XMLFile = $Destination + "\" + $XMLFile  
107:            $Switches = "/policy" + [char]32 + $XMLFile  
108:       }  
109:       #$Switches = " "  
110:       Write-Host "Updating BIOS....." -NoNewline  
111:       $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode  
112:       If ($ErrCode -eq 0) {  
113:            Write-Host "Success" -ForegroundColor Yellow  
114:       } else {  
115:            Write-Host "Failed with error code"$ErrCode -ForegroundColor Red  
116:       }  
117:  }  
118:    
119:  #Copy contents of the Dell Command | Update folder to the WinPE directory  
120:  Copy-Folder -SourceFolder $Source -DestinationFolder $Destination -Subfolders $true -Mirror $true  
121:  #Copy msi.dll to the WinPE system32 folder to make msiexec.exe functional  
122:  Copy-Item -Path $Destination"\msi.dll" -Destination "x:\windows\system32" -Force  
123:  #Execute the dcu-cli.exe to update the BIOS  
124:  Update-BIOS  
125:    

Copyright © 2013 Mick's IT Blogs™ is a registered trademark.