31 May 2016

PowerShell: Set Windows Features with Verification

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]

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.

NOTE: I just verified this fix also works with Windows 10 1709 using MDT 6.3.8450.1000.

20 May 2016

Initiating SCCM Actions with Verification

The latest script I am writing requires me to initiate both a software updates scan cycle and a software updates deployment evaluation cycle before continuing with the script. The actions needed to be completed before the script can continue, which is what started me on the path to writing this script. I figured if I was going to write two functions for those actions, I would write functions for the rest of the actions. I do realize there are more actions out there that I could include, but for now, I only included the ones in the Actions tab of the Configuration Manager Properties. Thanks to Eswar Knoeti's blog post, I had a reference to refer to on what log files to scan for the action being taken.

The way these scripts work, they kick off an action and then monitor the log file associated with that action until the completion/failed message appears. I have also included a 5 minute maximum wait time if something goes wrong and the action does not complete successfully, and a new message appears. I have noticed that some actions will not initiate if they have been initiated recently. For those, the status will return as failed.

I was going to originally include parameters so that you could select which functions to execute at command line. I chose not to because I figured that functions will likely be taken from this script to be included in other scripts and it is just as easy to comment out ones you do not want to execute between lines 446 and 456.

This has worked great in my environment. There maybe new messages in the log files that you may encounter that cause the function to not read the log file correctly. If so, please write a comment about that in the blog so that I can update the script. Thanks in advance.

With the help of Sapien's PowerShell Studio, I wrote the following script. You can download the script from here.


1:  <#  
2:       .SYNOPSIS  
3:            Initiate SCCM actions  
4:         
5:       .DESCRIPTION  
6:            This script will initiate SCCM actions and wait until the action is complete before continuing.   
7:         
8:       .EXAMPLE  
9:            powershell.exe -executionpolicy bypass -file SCCMActions.ps1  
10:         
11:       .NOTES  
12:       ===========================================================================  
13:        Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.122  
14:        Created on:       5/20/2016 2:28 PM  
15:        Created by:       Mick Pletcher  
16:        Organization:         
17:        Filename:         SCCMActions.ps1  
18:       ===========================================================================  
19:  #>  
20:  [CmdletBinding()]  
21:  param ()  
22:    
23:  function Get-CurrentDate {  
24:  <#  
25:       .SYNOPSIS  
26:            Get the current date and return formatted value  
27:         
28:       .DESCRIPTION  
29:            Return the current date in the following format: mm-dd-yyyy  
30:         
31:       .NOTES  
32:            Additional information about the function.  
33:  #>  
34:         
35:       [CmdletBinding()][OutputType([string])]  
36:       param ()  
37:         
38:       $CurrentDate = Get-Date  
39:       $CurrentDate = $CurrentDate.ToShortDateString()  
40:       $CurrentDate = $CurrentDate -replace "/", "-"  
41:       If ($CurrentDate[2] -ne "-") {  
42:            $CurrentDate = $CurrentDate.Insert(0, "0")  
43:       }  
44:       If ($CurrentDate[5] -ne "-") {  
45:            $CurrentDate = $CurrentDate.Insert(3, "0")  
46:       }  
47:       Return $CurrentDate  
48:  }  
49:    
50:  function Invoke-ApplicationDeploymentEvaluationCycle {  
51:       $Completed = $false  
52:       $StartTime = Get-Date -UFormat %R  
53:       $CurrentDate = Get-CurrentDate  
54:       Write-Host "Running Application Deployment Evaluation Cycle....." -NoNewline  
55:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000121"  
56:       Do {  
57:            Start-Sleep -Seconds 1  
58:            $CurrentTime = Get-Date -UFormat %R  
59:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
60:            $Log = Get-Content $env:windir"\ccm\logs\DCMReporting.log"  
61:            $Count = $Log.count  
62:            $Count = $Count - 1  
63:            $Log = $Log[$Count]  
64:            $LogTable = $Log.split("<")[-1]  
65:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
66:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
67:            $LogTime = $LogTable.time.Substring(0, 5)  
68:            [datetime]$StringTime = $LogTable.time  
69:            If (($Log -like "*FinalRelease*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
70:                 Write-Host "Completed" -ForegroundColor Yellow  
71:                 $Completed = $true  
72:            }  
73:            If ($TimeDifference.Minutes -ge 5) {  
74:                 Write-Host "Failed" -ForegroundColor Yellow  
75:                 $Completed = $true  
76:            }  
77:       } while ($Completed -eq $false)  
78:  }  
79:    
80:  function Invoke-DiscoveryDataCollectionCycle {  
81:       $Completed = $false  
82:       $StartTime = Get-Date -UFormat %R  
83:       $CurrentDate = Get-CurrentDate  
84:       Write-Host "Running Discovery Data Collection Cycle....." -NoNewline  
85:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000003"  
86:       Do {  
87:            Start-Sleep -Seconds 1  
88:            $CurrentTime = Get-Date -UFormat %R  
89:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
90:            $Log = Get-Content $env:windir"\ccm\logs\InventoryAgent.log"  
91:            $Count = $Log.count  
92:            $Count = $Count - 1  
93:            $Log = $Log[$Count]  
94:            $LogTable = $Log.split("<")[-1]  
95:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
96:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
97:            $LogTime = $LogTable.time.Substring(0, 5)  
98:            [datetime]$StringTime = $LogTable.time  
99:            If (($Log -like "*End of message processing*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
100:                 Write-Host "Completed" -ForegroundColor Yellow  
101:                 $Completed = $true  
102:            }  
103:            If ($TimeDifference.Minutes -ge 5) {  
104:                 Write-Host "Failed" -ForegroundColor Yellow  
105:                 $Completed = $true  
106:            }  
107:       } while ($Completed -eq $false)  
108:  }  
109:    
110:  function Invoke-FileCollectionCycle {  
111:       $Completed = $false  
112:       $StartTime = Get-Date -UFormat %R  
113:       $CurrentDate = Get-CurrentDate  
114:       Write-Host "Running File Collection Cycle....." -NoNewline  
115:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000010"  
116:       Do {  
117:            Start-Sleep -Seconds 1  
118:            $CurrentTime = Get-Date -UFormat %R  
119:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
120:            $Log = Get-Content $env:windir"\ccm\logs\InventoryAgent.log"  
121:            $Count = $Log.count  
122:            $Count = $Count - 1  
123:            $Log = $Log[$Count]  
124:            $LogTable = $Log.split("<")[-1]  
125:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
126:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
127:            $LogTime = $LogTable.time.Substring(0, 5)  
128:            [datetime]$StringTime = $LogTable.time  
129:            If ((($Log -like "*Action completed*") -or ($Log -like "*Exiting as no items to collect*")) -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
130:                 Write-Host "Completed" -ForegroundColor Yellow  
131:                 $Completed = $true  
132:            }  
133:            If ($TimeDifference.Minutes -ge 5) {  
134:                 Write-Host "Failed" -ForegroundColor Yellow  
135:                 $Completed = $true  
136:            }  
137:       } while ($Completed -eq $false)  
138:  }  
139:    
140:  function Invoke-HardwareInventoryCycle {  
141:       $Completed = $false  
142:       $StartTime = Get-Date -UFormat %R  
143:       $CurrentDate = Get-CurrentDate  
144:       Write-Host "Running Hardware Inventory Cycle....." -NoNewline  
145:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000001"  
146:       Do {  
147:            Start-Sleep -Seconds 1  
148:            $CurrentTime = Get-Date -UFormat %R  
149:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
150:            $Log = Get-Content $env:windir"\ccm\logs\InventoryAgent.log"  
151:            $Count = $Log.count  
152:            $Count = $Count - 1  
153:            $Log = $Log[$Count]  
154:            $LogTable = $Log.split("<")[-1]  
155:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
156:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
157:            $LogTime = $LogTable.time.Substring(0, 5)  
158:            [datetime]$StringTime = $LogTable.time  
159:            If (($Log -like "*End of message processing*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
160:                 Write-Host "Completed" -ForegroundColor Yellow  
161:                 $Completed = $true  
162:            }  
163:            If (($Log -like "*already in queue. Message ignored.*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
164:                 Write-Host "Ignored" -ForegroundColor Red  
165:                 $Completed = $true  
166:            }  
167:            If ($TimeDifference.Minutes -ge 5) {  
168:                 Write-Host "Failed" -ForegroundColor Yellow  
169:                 $Completed = $true  
170:            }  
171:       } while ($Completed -eq $false)  
172:  }  
173:    
174:  function Invoke-MachinePolicyEvaluationCycle {  
175:       $Completed = $false  
176:       $StartTime = Get-Date -UFormat %r  
177:       $StartTime = $StartTime.Split(' ')  
178:       $StartTime = $StartTime[0]  
179:       $StartTime = $StartTime.Substring(0, $StartTime.Length - 3)  
180:       $CurrentDate = Get-CurrentDate  
181:       Write-Host "Running Machine Policy Evaluation Cycle....." -NoNewline  
182:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000022"  
183:       Do {  
184:            Start-Sleep -Seconds 1  
185:            $CurrentTime = Get-Date -UFormat %R  
186:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
187:            $Log = Get-Content $env:windir"\ccm\logs\PolicyEvaluator.log"  
188:            $Count = $Log.count  
189:            $LogTime = $Log[$Count - 6]  
190:            $LogTime = $LogTime.Split('"')  
191:            $LogTime = $LogTime[1]  
192:            $LogTime = [management.managementdatetimeconverter]::ToDateTime($LogTime)  
193:            $LogDate = $LogTime  
194:            $LogTime = $LogTime.ToShortTimeString()  
195:            $LogTime = $Logtime.Split(' ')  
196:            $LogTime = $LogTime[0]  
197:            If ($LogTime[2] -ne ":") {  
198:                 $LogTime = $LogTime.Insert(0, "0")  
199:            }  
200:            $LogDate = $LogDate.ToShortDateString()  
201:            $LogDate = Get-CurrentDate  
202:            $LogStatus = $Log[$Count - 9]  
203:            If (($LogStatus -like "*instance of CCM_PolicyAgent_PolicyEvaluationComplete*") -and ($CurrentDate -eq $LogDate) -and ($LogTime -ge $StartTime)) {  
204:                 Write-Host "Completed" -ForegroundColor Yellow  
205:                 $Completed = $true  
206:            }  
207:            If ($TimeDifference.Minutes -ge 5) {  
208:                 Write-Host "Failed" -ForegroundColor Yellow  
209:                 $Completed = $true  
210:            }  
211:       } while ($Completed -eq $false)  
212:  }  
213:    
214:  function Invoke-MachinePolicyRetrievalCycle {  
215:       $Completed = $false  
216:       $StartTime = Get-Date -UFormat %r  
217:       $StartTime = $StartTime.Split(' ')  
218:       $StartTime = $StartTime[0]  
219:       $StartTime = $StartTime.Substring(0, $StartTime.Length - 3)  
220:       $CurrentDate = Get-CurrentDate  
221:       Write-Host "Running Machine Policy Retrieval Cycle....." -NoNewline  
222:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000021"  
223:       Do {  
224:            Start-Sleep -Seconds 1  
225:            $CurrentTime = Get-Date -UFormat %R  
226:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
227:            $Log = Get-Content $env:windir"\ccm\logs\PolicyEvaluator.log"  
228:            $Count = $Log.count  
229:            $LogTime = $Log[$Count - 6]  
230:            $LogTime = $LogTime.Split('"')  
231:            $LogTime = $LogTime[1]  
232:            $LogTime = [management.managementdatetimeconverter]::ToDateTime($LogTime)  
233:            $LogDate = $LogTime  
234:            $LogTime = $LogTime.ToShortTimeString()  
235:            $LogTime = $Logtime.Split(' ')  
236:            $LogTime = $LogTime[0]  
237:            If ($LogTime[2] -ne ":") {  
238:                 $LogTime = $LogTime.Insert(0, "0")  
239:            }  
240:            $LogDate = $LogDate.ToShortDateString()  
241:            $LogDate = Get-CurrentDate  
242:            $LogStatus = $Log[$Count - 9]  
243:            If (($LogStatus -like "*instance of CCM_PolicyAgent_PolicyEvaluationComplete*") -and ($CurrentDate -eq $LogDate) -and ($LogTime -ge $StartTime)) {  
244:                 Write-Host "Completed" -ForegroundColor Yellow  
245:                 $Completed = $true  
246:            }  
247:            If ($TimeDifference.Minutes -ge 5) {  
248:                 Write-Host "Failed" -ForegroundColor Yellow  
249:                 $Completed = $true  
250:            }  
251:       } while ($Completed -eq $false)  
252:  }  
253:    
254:  function Invoke-SoftwareInventoryCycle {  
255:       $Completed = $false  
256:       $StartTime = Get-Date -UFormat %R  
257:       $CurrentDate = Get-CurrentDate  
258:       Write-Host "Running Software Inventory Cycle....." -NoNewline  
259:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000002"  
260:       Do {  
261:            Start-Sleep -Seconds 1  
262:            $CurrentTime = Get-Date -UFormat %R  
263:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
264:            $Log = Get-Content $env:windir"\ccm\logs\InventoryAgent.log"  
265:            $Count = $Log.count  
266:            $Count = $Count - 1  
267:            $Log = $Log[$Count]  
268:            $LogTable = $Log.split("<")[-1]  
269:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
270:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
271:            $LogTime = $LogTable.time.Substring(0, 5)  
272:            [datetime]$StringTime = $LogTable.time  
273:            If (($Log -like "*Initialization completed in*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
274:                 Write-Host "Completed" -ForegroundColor Yellow  
275:                 $Completed = $true  
276:            }  
277:            If ($TimeDifference.Minutes -ge 5) {  
278:                 Write-Host "Failed" -ForegroundColor Yellow  
279:                 $Completed = $true  
280:            }  
281:       } while ($Completed -eq $false)  
282:  }  
283:    
284:  function Invoke-SoftwareMeteringUsageReportCycle {  
285:       $Completed = $false  
286:       $StartTime = Get-Date -UFormat %R  
287:       $CurrentDate = Get-CurrentDate  
288:       Write-Host "Running Software Metering Usage Report Cycle....." -NoNewline  
289:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000031"  
290:       Do {  
291:            Start-Sleep -Seconds 1  
292:            $CurrentTime = Get-Date -UFormat %R  
293:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
294:            $Log = Get-Content $env:windir"\ccm\logs\SWMTRReportGen.log"  
295:            $Count = $Log.count  
296:            $Count = $Count - 1  
297:            $Log = $Log[$Count]  
298:            $LogTable = $Log.split("<")[-1]  
299:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
300:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
301:            $LogTime = $LogTable.time.Substring(0, 5)  
302:            [datetime]$StringTime = $LogTable.time  
303:            If ((($Log -like "*No usage data found to generate software metering report*") -or ($Log -like "*Successfully generated report header*") -or ($Log -like "*Message ID of sent message*")) -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
304:                 Write-Host "Completed" -ForegroundColor Yellow  
305:                 $Completed = $true  
306:            }  
307:            If ($TimeDifference.Minutes -ge 5) {  
308:                 Write-Host "Failed" -ForegroundColor Yellow  
309:                 $Completed = $true  
310:            }  
311:       } while ($Completed -eq $false)  
312:  }  
313:    
314:  function Invoke-SoftwareUpdatesDeploymentEvaluationCycle {  
315:  <#  
316:       .SYNOPSIS  
317:            Scan for software updates that are out of compliance  
318:         
319:       .DESCRIPTION  
320:            Initiates a scan for software updates compliance. Before client computers can scan for software update compliance, the software updates environment must be configured.  
321:         
322:       .NOTES  
323:            Additional information about the function.  
324:  #>  
325:         
326:       [CmdletBinding()]  
327:       param ()  
328:         
329:       $Completed = $false  
330:       $StartTime = Get-Date -UFormat %R  
331:       $CurrentDate = Get-CurrentDate  
332:       Write-Host "Running Software Updates Deployment Evaluation Cycle....." -NoNewline  
333:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000108"  
334:       Do {  
335:            Start-Sleep -Seconds 1  
336:            $CurrentTime = Get-Date -UFormat %R  
337:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
338:            $Log = Get-Content $env:windir"\ccm\logs\ScanAgent.log"  
339:            $Count = $Log.count  
340:            $Count = $Count - 1  
341:            $Log = $Log[$Count]  
342:            $LogTable = $Log.split("<")[-1]  
343:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
344:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
345:            $LogTime = $LogTable.time.Substring(0, 5)  
346:            [datetime]$StringTime = $LogTable.time  
347:            If (($Log -like "*Calling back to client on Scan request complete*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
348:                 Write-Host "Completed" -ForegroundColor Yellow  
349:                 $Completed = $true  
350:            }  
351:            If ($TimeDifference.Minutes -ge 5) {  
352:                 Write-Host "Failed" -ForegroundColor Yellow  
353:                 $Completed = $true  
354:            }  
355:       } while ($Completed -eq $false)  
356:  }  
357:    
358:  function Invoke-SoftwareUpdatesScanCycle {  
359:       $Completed = $false  
360:       $StartTime = Get-Date -UFormat %R  
361:       $CurrentDate = Get-CurrentDate  
362:       Write-Host "Running Software Updates Scan Cycle....." -NoNewline  
363:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000113"  
364:       Do {  
365:            Start-Sleep -Seconds 1  
366:            $CurrentTime = Get-Date -UFormat %R  
367:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
368:            $Log = Get-Content $env:windir"\ccm\logs\scanagent.log"  
369:            $Count = $Log.count  
370:            $Count = $Count - 1  
371:            $Log = $Log[$Count]  
372:            $LogTable = $Log.split("<")[-1]  
373:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
374:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
375:            $LogTime = $LogTable.time.Substring(0, 5)  
376:            [datetime]$StringTime = $LogTable.time  
377:            If (($Log -like "*scan completion received*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
378:                 Write-Host "Completed" -ForegroundColor Yellow  
379:                 $Completed = $true  
380:            }  
381:            If ($TimeDifference.Minutes -ge 5) {  
382:                 Write-Host "Failed" -ForegroundColor Yellow  
383:                 $Completed = $true  
384:            }  
385:       } while ($Completed -eq $false)  
386:  }  
387:    
388:  function Invoke-WindowsInstallerSourceListUpdateCycle {  
389:       $Completed = $false  
390:       $StartTime = Get-Date -UFormat %R  
391:       $CurrentDate = Get-CurrentDate  
392:       Write-Host "Running Windows Installer Source List Update Cycle....." -NoNewline  
393:       Start-ConfigurationManagerClientScan -ScheduleID "00000000-0000-0000-0000-000000000032"  
394:       Do {  
395:            Start-Sleep -Seconds 1  
396:            $CurrentTime = Get-Date -UFormat %R  
397:            $TimeDifference = New-TimeSpan -Start $StartTime -End $CurrentTime  
398:            $Log = Get-Content $env:windir"\ccm\logs\SrcUpdateMgr.log"  
399:            $Count = $Log.count  
400:            $Count = $Count - 1  
401:            $Log = $Log[$Count]  
402:            $LogTable = $Log.split("<")[-1]  
403:            $LogTable = $LogTable.Substring(0, $LogTable.length - 1) -replace ' ', ';'  
404:            $LogTable = "@{$($LogTable)}" | Invoke-Expression  
405:            $LogTime = $LogTable.time.Substring(0, 5)  
406:            [datetime]$StringTime = $LogTable.time  
407:            If (($Log -like "*MSI update source list task finished successfully*") -and ($CurrentDate -eq $LogTable.date) -and ($LogTime -ge $StartTime)) {  
408:                 Write-Host "Completed" -ForegroundColor Yellow  
409:                 $Completed = $true  
410:            }  
411:            If ($TimeDifference.Minutes -ge 5) {  
412:                 Write-Host "Failed" -ForegroundColor Yellow  
413:                 $Completed = $true  
414:            }  
415:       } while ($Completed -eq $false)  
416:  }  
417:    
418:  function Start-ConfigurationManagerClientScan {  
419:  <#  
420:       .SYNOPSIS  
421:            Initiate Configuration Manager Client Scan  
422:         
423:       .DESCRIPTION  
424:            This will initiate an SCCM action  
425:         
426:       .PARAMETER ScheduleID  
427:            GUID ID of the SCCM action  
428:         
429:       .NOTES  
430:            Additional information about the function.  
431:  #>  
432:         
433:       [CmdletBinding()]  
434:       param  
435:       (  
436:                 [ValidateSet('00000000-0000-0000-0000-000000000121', '00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000010', '00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000021', '00000000-0000-0000-0000-000000000022', '00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000031', '00000000-0000-0000-0000-000000000108', '00000000-0000-0000-0000-000000000113', '00000000-0000-0000-0000-000000000111', '00000000-0000-0000-0000-000000000026', '00000000-0000-0000-0000-000000000027', '00000000-0000-0000-0000-000000000032')]$ScheduleID  
437:       )  
438:         
439:       $WMIPath = "\\" + $env:COMPUTERNAME + "\root\ccm:SMS_Client"  
440:       $SMSwmi = [wmiclass]$WMIPath  
441:       $Action = [char]123 + $ScheduleID + [char]125  
442:       [Void]$SMSwmi.TriggerSchedule($Action)  
443:  }  
444:    
445:  Clear-Host  
446:  Invoke-SoftwareUpdatesScanCycle  
447:  Invoke-SoftwareUpdatesDeploymentEvaluationCycle  
448:  Invoke-ApplicationDeploymentEvaluationCycle  
449:  Invoke-DiscoveryDataCollectionCycle  
450:  Invoke-FileCollectionCycle  
451:  Invoke-HardwareInventoryCycle  
452:  Invoke-MachinePolicyEvaluationCycle  
453:  Invoke-MachinePolicyRetrievalCycle  
454:  Invoke-SoftwareInventoryCycle  
455:  Invoke-SoftwareMeteringUsageReportCycle  
456:  Invoke-WindowsInstallerSourceListUpdateCycle  
457:    

18 May 2016

Configure PowerShell Settings

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

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: