Loading...

Follow Mick's IT Blogs on Feedspot

Continue with Google
Continue with Facebook
or

Valid
Mick's IT Blogs by Mick Pletcher - 5d ago
I wrote an article about three years ago on conditional task sequence reboots. It used the built-in reboot task sequence that was initiated only if any of the three conditions were met. The problem was a fourth condition that could not be tested for because a WMI query is the only way to test and MDT conditions do not incorporate WMI.

Recently, I revisited this, and it occurred to me how to incorporate the WMI query after going through the ZTIWindowsUpdate.wsf and seeing how it initiated reboots. I abandoned the built-in reboot and wrote a PowerShell script that can test all four conditions and then connect to the TSEnvironment object to start a reboot.

The four conditions the script checks for are:

  • Component Based Servicing
  • Windows Updates
  • Pending Files Rename
  • Pending reboot from SCCM installs
The script will iterate through all four conditions. If a condition is met, it will then connect to the TSEnvironment object and request a reboot by setting SMSTSRebootRequested to true. Once the script is finished, the system will reboot and then proceed to the next task.

I also included the commented out SMSTSRetryRequested in the script. This command will cause the task sequence to rerun this script. I included it in here so if you want to take the code from this script and incorporate it into another script that will rerun it after the reboot, it is there. 

The first thing to do is to copy the script to the scripts (%SCRIPTROOT%) directory. As you can see in the screenshot below, I used a Run Command Line task sequence.



The command line is as follows:

 powershell.exe -executionpolicy bypass -file "%SCRIPTROOT%\ZTIConditionalReboot.ps1"  


Finally, here is the script. You can download it from my GitHub site.

 <#  
.SYNOPSIS
Zero Touch Conditional Reboot

.DESCRIPTION
This script will check four flags on the system to see if a reboot is required. If one of the flags is tripped, then this script will initiate a reboot in MDT so that will come back up and start at the proceeding task. I have included the commented out SMSTSRetryRequested in the script so if you want to incorporate the code from this script into another one that will need to be rerun again once the reboot completes.

.NOTES
===========================================================================
Created with: SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.142
Created on: 7/12/2019 2:53 PM
Created by: Mick Pletcher
Organization: Waller Lansden Dortch & Davis, LLP.
Filename: ZTIConditionalReboot.ps1
===========================================================================
#>
[CmdletBinding()]
param ()

function Enable-Reboot {
<#
.SYNOPSIS
Request MDT Reboot

.DESCRIPTION
A detailed description of the Enable-Reboot function.
#>

[CmdletBinding()]
param ()

$TaskSequence = New-Object -ComObject Microsoft.SMS.TSEnvironment
#Reboot the machine this command line task sequence finishes
$TaskSequence.Value('SMSTSRebootRequested') = $true
#Rerun this task when the reboot finishes
#$TaskSequence.Value('SMSTSRetryRequested') = $true
}

#Component Based Reboot
If ((Get-ChildItem "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -ErrorAction SilentlyContinue) -ne $null) {
Enable-Reboot
#Windows Update Reboot
} elseif ((Get-Item -Path "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -ErrorAction SilentlyContinue) -ne $null) {
Enable-Reboot
#Pending Files Rename Reboot
} elseif ((Get-ItemProperty -Path "REGISTRY::HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -ErrorAction SilentlyContinue) -ne $null) {
Enable-Reboot
#Pending SCCM Reboot
} elseif ((([wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities").DetermineIfRebootPending().RebootPending) -eq $true) {
Enable-Reboot
} else {
Exit 0
}

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Over the past two months, I deployed the Windows 10 1809 to all Windows 10 machines. We got through 80% of the machines with successful deployments until we reached those last 20% where they did not have enough disk space for both downloading the package and installing it. The package and install require roughly 15 gigs of space, 5 gigs for the package and 10 gigs for the installation.

We changed the distribution points option to run the program from the distribution point to drop down the 15 gig requirement to 10 gigs, which also significantly reduced the number of machines without enough space.


Once we made that change, systems started getting the message shown below. This caused the package to stall since there was no one to click run.



To get around this issue, we added the FQDN of each distribution point to the intranet trusted sites. The FQDN is added like shown here: \\<SCCMServer.contoso.com>\ and set as security zone 1. This fixed that issue. 
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Over the past two months, I deployed the Windows 10 1809 to all Windows 10 machines. We got through 80% of the machines with successful deployments until we reached those last 20% where they did not have enough disk space for both downloading the package and installing it. The package and install require roughly 15 gigs of space, 5 gigs for the package and 10 gigs for the installation.

We changed the distribution points option to run the program from the distribution point to drop down the 15 gig requirement to 10 gigs, which also significantly reduced the number of machines without enough space.


Once we made that change, systems started getting the message shown below. This caused the package to stall since there was no one to click run.



To get around this issue, we added the FQDN of each distribution point to the intranet trusted sites. The FQDN is added like shown here: \\<SCCMServer.contoso.com>\ and set as security zone 1. This fixed that issue. 
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Recently, we decided to change the power scheme on machines during the build. This can be quickly done using the powercfg.exe, but I wanted to be sure it always set correctly. Plus, the GUID associated with a power scheme can be different, so I wanted to specify the power scheme by the name.

This PowerShell one-liner will set the power scheme on a machine to the scheme defined in the variable $Setting. If you do a powercfg.exe /l, you will see the name displayed to the right of the GUID in parenthesis. That is what you define in the above variable. The one-liner will then query powercfg to check if it matches the $Setting variable. If it does, it exits with an error code 0. If it does not match, then it sets the power scheme to the GUID from the query and then rechecks to make sure the setting was configured. If it still does not match, then it exits with an error code 1. 

To use this in a one-liner, you need to define the $Setting inside the one-liner below. This may differ on machines, so do the above query to see what is defined in your environment. Place this one-liner in a command line task sequence and you are done.


 powershell.exe -executionpolicy bypass -command "&{$Setting='Balanced';$Output = powercfg.exe /l | Where-Object {$_ -like ('*' + $Setting + '*')};If ($Output.Contains('*') -eq $true) {Write-Host ($Setting + [char]32 + 'is configured');Exit 0} else {$Output = powercfg.exe /s $output.split(' ')[3]; $Output = powercfg.exe /l | Where-Object {$_ -like ('*' + $Setting + '*')};If ($Output.Contains('*') -eq $true) {Write-Host ($Setting + [char]32 + 'Powercfg is configured');Exit 0} else {Write-Host 'Powercfg failed';Exit 1}}}"  

Below is a pic of what it looks like when used in a Run Command Line task sequence. IMO, it makes managing PowerShell scripts easier when they are contained within the task sequence.


  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
If you have been wanting to wake your Dell systems up from sleep, hibernate, or shutdown states, this is how you do it. Starting out with this article from Dell, I got the list of things needed to set up the system for WOL. There are three areas that have to be configured on Dell systems, at least for the systems we have which range from the Optiplex 990 to the Latitude 7490. The areas are BIOS, advanced NIC, and power management settings. This site helped with the final setting to disable fast startup, which is required. WOL did not work on our systems until I implemented this final setting.

Before implementing this baseline, you will need to make sure Dell Command | Configure is installed on all systems. To ensure this, I have it deployed as an application to all Dell systems. Dell Command | Configure is what the baseline PowerShell scripts use to query and configure the BIOS settings. I also made a collection called All Dell Systems since we also have a few Microsoft Surfaces.

NOTE: This was created on April 15, 2019. New Dell models and BIOS updates are constantly released. It is likely there will be changes that need to be made in the future to these scripts to work with those updates.

The Wake-On-LAN Compliance item is set up to use a script that returns a Boolean value as shown below.


The discovery script is the following PowerShell script:


 ##Find Dell Command | Configure for 64-bit  
$CCTK = Get-ChildItem -Path ${env:ProgramFiles(x86)}, $env:ProgramFiles -Filter cctk.exe -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.Directory -like '*x86_64*'}
##Get all available Dell Command | Configure commands for current system
$Commands = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName -h} -ErrorAction SilentlyContinue
##Configure BIOS --wakeonlan=enable
#Test if wakeonlan exists on current system
If ($Commands -like '*wakeonlan*') {
[string]$WakeOnLANSetting = 'wakeonlan=enable'
[string]$Output = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName --wakeonlan} -ErrorAction SilentlyContinue
If ($Output -ne $WakeOnLANSetting) {
$WakeOnLAN = $false
} else {
$WakeOnLAN = $true
}
}
##Configure BIOS --deepsleepctrl=disable
#Test if deepsleepctrl exists on current system
If ($Commands -like '*deepsleepctrl*') {
[string]$DeepSleepCtrlSetting = 'deepsleepctrl=disable'
[string]$Output = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName --deepsleepctrl} -ErrorAction SilentlyContinue
If ($Output -ne $DeepSleepCtrlSetting) {
$DeepSleepCtrl = $false
} else {
$DeepSleepCtrl = $true
}
}
##Configure BIOS --blocks3=disable
#Test if blocks3 exists on current system
If ($Commands -like '*blocks3*') {
[string]$BlockS3Setting = 'blocks3=disable'
[string]$Output = Invoke-Command -ScriptBlock { c:\Windows\system32\cmd.exe /c $CCTK.FullName --blocks3} -ErrorAction SilentlyContinue
If ($Output -ne $BlockS3Setting) {
$BlockS3 = $false
} else {
$BlockS3 = $true
}
}
##Configure BIOS --cstatesctrl=disable
#Test if cstatesctrl exists on current system
If ($Commands -like '*cstatesctrl*') {
[string]$CStateCTRLSetting = 'cstatesctrl=disable'
[string]$Output = Invoke-Command -ScriptBlock { c:\Windows\system32\cmd.exe /c $CCTK.FullName --cstatesctrl} -ErrorAction SilentlyContinue
If ($Output -ne $CStateCTRLSetting) {
$CStateCTRL = $false
} else {
$CStateCTRL = $true
}
}
##Disable Energy Efficient Ethernet
#Energy Efficient Ethernet disable registry value
$RegistryValue = '0'
#Find ethernet adapter
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter | Where-Object {$_.DisplayName -like '*Efficient Ethernet*'}).DisplayName
#Test for presence of Energy-Efficient Ethernet
If ($DisplayName -like '*Efficient Ethernet*') {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
If ($CurrentState -ne $RegistryValue) {
$EnergyEfficientEthernet = $false
} else {
$EnergyEfficientEthernet = $true
}
}
##Enable Wake on Magic Packet
$State = 'Enabled'
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter | Where-Object {$_.DisplayName -like '*Magic Packet*'}).DisplayName
#Test if Magic Packet exists
If ($DisplayName -like '*Magic Packet*') {
[string]$CurrentState = (Get-NetAdapterPowerManagement -Name $Adapter).WakeOnMagicPacket
If ($CurrentState -ne $State) {
$WakeOnMagicPacket = $false
} else {
$WakeOnMagicPacket = $true
}
}
##Disable Shutdown Wake-On-Lan
$RegistryValue = '0'
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq 'Shutdown Wake-On-Lan'}).DisplayName
If ($DisplayName -eq 'Shutdown Wake-On-Lan') {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
If ($CurrentState -ne $RegistryValue) {
$ShutdownWakeOnLAN = $false
} else {
$ShutdownWakeOnLAN = $true
}
}
##Enable Allow the computer to turn off this device
$KeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\'
#Test if KeyPath exists
If ((Test-Path $KeyPath) -eq $true) {
$PnPValue = 256
$Adapter = Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}
foreach ($Entry in (Get-ChildItem $KeyPath -ErrorAction SilentlyContinue).Name) {
If ((Get-ItemProperty REGISTRY::$Entry).DriverDesc -eq $Adapter.InterfaceDescription) {
$Value = (Get-ItemProperty REGISTRY::$Entry).PnPCapabilities
If ($Value -ne $PnPValue) {
$PowerManagement = $false
} else {
$PowerManagement = $true
}
}
}
}
##Disable Fast Startup
$KeyPath = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power"
#Test if KeyPath exists
If ((Test-Path -Path ('REGISTRY::' + $KeyPath)) -eq $true) {
If ((Get-ItemProperty -Path ('REGISTRY::' + $KeyPath)).HiberbootEnabled -eq 0) {
$FastStartup = $false
} else {
$FastStartup = $true
}
}
#Write-Host 'Wake-On-LAN:'$WakeOnLAN
#Write-Host 'Deep Sleep Control:'$DeepSleepCtrl
#Write-Host 'BlockS3:'$BlockS3
#Write-Host 'CState Control:'$CStateCTRL
#Write-Host 'Energy Efficient Ethernet:'$EnergyEfficientEthernet
#Write-Host 'Wake-On-Magic-Packet:'$WakeOnMagicPacket
#Write-Host 'Shutdown Wake-On-LAN:'$ShutdownWakeOnLAN
#Write-Host 'Allow Computer to Turn Off this Device:'$PowerManagement
If ((($WakeOnLAN -eq $null) -or ($WakeOnLAN -eq $true)) -and ($FastStartup -eq $false) -and (($DeepSleepCtrl -eq $null) -or ($DeepSleepCtrl -eq $true)) -and (($BlockS3 -eq $null) -or ($BlockS3 -eq $true)) -and (($CStateCTRL -eq $null) -or ($CStateCTRL -eq $true)) -and (($EnergyEfficientEthernet -eq $null) -or ($EnergyEfficientEthernet -eq $true)) -and (($WakeOnMagicPacket -eq $null) -or ($WakeOnMagicPacket -eq $true)) -and (($ShutdownWakeOnLAN -eq $null) -or ($ShutdownWakeOnLAN -eq $true)) -and (($PowerManagement -eq $null) -or ($PowerManagement -eq $true))) {
echo $true
} else {
echo $false
}


The remediation script is as follows:


 ##Find Dell Command | Configure for 64-bit  
$CCTK = Get-ChildItem -Path ${env:ProgramFiles(x86)}, $env:ProgramFiles -Filter cctk.exe -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.Directory -like '*x86_64*'}
##Get all available Dell Command | Configure commands for current system
$Commands = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName -h} -ErrorAction SilentlyContinue
##Configure BIOS --wakeonlan=enable
#Test if wakeonlan exists on current system
If ($Commands -like '*wakeonlan*') {
[string]$WakeOnLANSetting = 'wakeonlan=enable'
[string]$Output = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName --wakeonlan} -ErrorAction SilentlyContinue
If ($Output -ne $WakeOnLANSetting) {
$ErrCode = (Start-Process -FilePath $CCTK.FullName -ArgumentList ('--' + $WakeOnLANSetting) -Wait -Passthru).ExitCode
If ($ErrCode -eq 0) {
$WakeOnLAN = $true
} elseif ($ErrCode -eq 119) {
$WakeOnLAN = $true
} else {
$WakeOnLAN = $false
}
Remove-Variable -Name ErrCode
} else {
$WakeOnLAN = $true
}
Remove-Variable -Name WakeOnLANSetting
Remove-Variable -Name Output
}
##Configure BIOS --deepsleepctrl=disable
#Test if deepsleepctrl exists on current system
If ($Commands -like '*deepsleepctrl*') {
[string]$DeepSleepCtrlSetting = 'deepsleepctrl=disable'
[string]$Output = Invoke-Command -ScriptBlock {c:\Windows\system32\cmd.exe /c $CCTK.FullName --deepsleepctrl} -ErrorAction SilentlyContinue
If ($Output -ne $DeepSleepCtrlSetting) {
$ErrCode = (Start-Process -FilePath $CCTK.FullName -ArgumentList ('--' + $DeepSleepCtrlSetting) -Wait -Passthru).ExitCode
If ($ErrCode -eq 0) {
$DeepSleepCtrl = $true
} elseif ($ErrCode -eq 119) {
$DeepSleepCtrl = $true
} else {
$DeepSleepCtrl = $false
}
Remove-Variable -Name ErrCode
}
Remove-Variable -Name DeepSleepCtrlSetting
Remove-Variable -Name Output
}
##Configure BIOS --blocks3=disable
#Test if blocks3 exists on current system
If ($Commands -like '*blocks3*') {
[string]$BlockS3Setting = 'blocks3=disable'
[string]$Output = Invoke-Command -ScriptBlock { c:\Windows\system32\cmd.exe /c $CCTK.FullName --blocks3} -ErrorAction SilentlyContinue
If ($Output -ne $BlockS3Setting) {
$ErrCode = (Start-Process -FilePath $CCTK.FullName -ArgumentList ('--' + $BlockS3Setting) -Wait -Passthru).ExitCode
If ($ErrCode -eq 0) {
$BlockS3 = $true
} elseif ($ErrCode -eq 119) {
$BlockS3 = $true
} else {
$BlockS3 = $false
}
Remove-Variable -Name ErrCode
} else {
$BlockS3 = $true
}
Remove-Variable -Name BlockS3Setting
Remove-Variable -Name Output
}
##Configure BIOS --cstatesctrl=disable
#Test if cstatesctrl exists on current system
If ($Commands -like '*cstatesctrl*') {
[string]$CStateCTRLSetting = 'cstatesctrl=disable'
[string]$Output = Invoke-Command -ScriptBlock { c:\Windows\system32\cmd.exe /c $CCTK.FullName --cstatesctrl} -ErrorAction SilentlyContinue
If ($Output -ne $CStateCTRLSetting) {
$ErrCode = (Start-Process -FilePath $CCTK.FullName -ArgumentList ('--' + $CStateCTRLSetting) -Wait -Passthru).ExitCode
If ($ErrCode -eq 0) {
$CStateCTRL = $true
} elseif ($ErrCode -eq 119) {
$CStateCTRL = $true
} else {
$CStateCTRL = $false
}
Remove-Variable -Name ErrCode
} else {
$CStateCTRL = $true
}
Remove-Variable -Name CStateCTRLSetting
Remove-Variable -Name Output
}
##Disable Energy Efficient Ethernet
#Energy Efficient Ethernet disable registry value
$RegistryValue = '0'
#Find ethernet adapter
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter | Where-Object {$_.DisplayName -like '*Efficient Ethernet*'}).DisplayName
#Test for presence of Energy-Efficient Ethernet
If ($DisplayName -like '*Efficient Ethernet*') {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
If ($CurrentState -ne $RegistryValue) {
Set-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName -RegistryValue $RegistryValue
Do {
Try {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
$Err = $false
} Catch {
$Err = $true
}
} While ($Err -eq $true)
If ($RegistryValue -eq $CurrentState) {
$EnergyEfficientEthernet = $true
} else {
$EnergyEfficientEthernet = $false
}
Remove-Variable -Name Err
} else {
$EnergyEfficientEthernet = $true
}
Remove-Variable -Name RegistryValue
Remove-Variable -Name Adapter
Remove-Variable -Name DisplayName
Remove-Variable -Name CurrentState
}
##Enable Wake on Magic Packet
$State = 'Enabled'
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter | Where-Object {$_.DisplayName -like '*Magic Packet*'}).DisplayName
#Test if Magic Packet exists
If ($DisplayName -like '*Magic Packet*') {
[string]$CurrentState = (Get-NetAdapterPowerManagement -Name $Adapter).WakeOnMagicPacket
If ($CurrentState -ne $State) {
Set-NetAdapterPowerManagement -Name $Adapter -WakeOnMagicPacket $State
Do {
Try {
[string]$CurrentState = (Get-NetAdapterPowerManagement -Name $Adapter).WakeOnMagicPacket
$Err = $false
} Catch {
$Err = $true
}
} While ($Err -eq $true)
If ($State -eq $CurrentState) {
$WakeOnMagicPacket = $true
} else {
$WakeOnMagicPacket = $false
}
Remove-Variable -Name Err
} else {
$WakeOnMagicPacket = $true
}
Remove-Variable -Name State
Remove-Variable -Name Adapter
Remove-Variable -Name DisplayName
Remove-Variable -Name CurrentState
}
##Disable Shutdown Wake-On-Lan
$RegistryValue = '0'
$Adapter = (Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}).Name
$DisplayName = (Get-NetAdapterAdvancedProperty -Name $Adapter -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq 'Shutdown Wake-On-Lan'}).DisplayName
If ($DisplayName -eq 'Shutdown Wake-On-Lan') {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
If ($CurrentState -ne $RegistryValue) {
Set-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName -RegistryValue $RegistryValue
Do {
Try {
[string]$CurrentState = (Get-NetAdapterAdvancedProperty -Name $Adapter -DisplayName $DisplayName).RegistryValue
$Err = $false
} Catch {
$Err = $true
}
} While ($Err -eq $true)
If ($RegistryValue -eq $CurrentState) {
$ShutdownWakeOnLAN = $true
} else {
$ShutdownWakeOnLAN = $false
}
Remove-Variable -Name Err
} else {
$ShutdownWakeOnLAN = $true
}
Remove-Variable -Name RegistryValue
Remove-Variable -Name Adapter
Remove-Variable -Name DisplayName
Remove-Variable -Name CurrentState
}
##Enable Allow the computer to turn off this device
$KeyPath = 'HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002bE10318}\'
#Test if KeyPath exists
If ((Test-Path $KeyPath) -eq $true) {
$PnPValue = 256
$Adapter = Get-NetAdapter | Where-Object {($_.Status -eq 'Up') -and ($_.PhysicalMediaType -eq '802.3')}
foreach ($Entry in (Get-ChildItem $KeyPath -ErrorAction SilentlyContinue).Name) {
If ((Get-ItemProperty REGISTRY::$Entry).DriverDesc -eq $Adapter.InterfaceDescription) {
$Value = (Get-ItemProperty REGISTRY::$Entry).PnPCapabilities
If ($Value -ne $PnPValue) {
Set-ItemProperty -Path REGISTRY::$Entry -Name PnPCapabilities -Value $PnPValue -Force
Disable-PnpDevice -InstanceId $Adapter.PnPDeviceID -Confirm:$false
Enable-PnpDevice -InstanceId $Adapter.PnPDeviceID -Confirm:$false
$Value = (Get-ItemProperty REGISTRY::$Entry).PnPCapabilities
}
If ($Value -eq $PnPValue) {
$PowerManagement = $true
} else {
$PowerManagement = $false
}
Remove-Variable -Name Value
}
}
Remove-Variable -Name PnPValue
Remove-Variable -Name Adapter
Remove-Variable -Name KeyPath
}
##Disable Fast Startup
$KeyPath = "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Power"
#Test if KeyPath exists
If ((Test-Path -Path ('REGISTRY::' + $KeyPath)) -eq $true) {
Set-ItemProperty -Path ('REGISTRY::' + $KeyPath) -Name 'HiberbootEnabled' -Value 0
If ((Get-ItemProperty -Path ('REGISTRY::' + $KeyPath)).HiberbootEnabled -eq 0) {
$FastStartup = $false
} else {
$FastStartup = $true
}
}
#Write-Host 'Wake-On-LAN:'$WakeOnLAN
#Write-Host 'Deep Sleep Control:'$DeepSleepCtrl
#Write-Host 'BlockS3:'$BlockS3
#Write-Host 'CState Control:'$CStateCTRL
#Write-Host 'Energy Efficient Ethernet:'$EnergyEfficientEthernet
#Write-Host 'Wake-On-Magic-Packet:'$WakeOnMagicPacket
#Write-Host 'Shutdown Wake-On-LAN:'$ShutdownWakeOnLAN
#Write-Host 'Allow Computer to Turn Off this Device:'$PowerManagement
If ((($WakeOnLAN -eq $null) -or ($WakeOnLAN -eq $true)) -and ($FastStartup -eq $false) -and (($DeepSleepCtrl -eq $null) -or ($DeepSleepCtrl -eq $true)) -and (($BlockS3 -eq $null) -or ($BlockS3 -eq $true)) -and (($CStateCTRL -eq $null) -or ($CStateCTRL -eq $true)) -and (($EnergyEfficientEthernet -eq $null) -or ($EnergyEfficientEthernet -eq $true)) -and (($WakeOnMagicPacket -eq $null) -or ($WakeOnMagicPacket -eq $true)) -and (($ShutdownWakeOnLAN -eq $null) -or ($ShutdownWakeOnLAN -eq $true)) -and (($PowerManagement -eq $null) -or ($PowerManagement -eq $true))) {
echo $true
} else {
echo $false
}



Finally, the compliance rule is as follows:


You may wonder why I included Remove-Variable cmdlets. I used those when I was debugging so it was easier to track variable values.

For the configuration baseline, I have it configured as shown below:


  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
In my list of recent security projects, I needed to ensure certain applications are present on systems by using SCCM application deployment. One of those applications was Dell Command | Configure. The issue with this application is the Dell Command | Update will update the application which in turn would register it as not installed to SCCM, thereby kicking off the installation again. That, in turn, would downgrade the application. There are three built-in options in SCCM to choose from that indicate whether an application is installed or not. Those are application GUID, files, and registry. The GUID typically changes every time an app is upgraded and the files and registry can change too. Luckily, this application never changes its name in the programs and features. The version field is typically what changes unless it is a significant upgrade.

The fourth option for confirming if an app is installed is custom method detection where you use a PowerShell script. That is the option I have used to make sure the Dell Command | Configure is registered as installed, no matter the version it has updated to. The following script can be used for this purpose. As you can see, I assigned the application name exactly as it appears in the programs and features to the variable $Application. If a company does include the version in the application name, then you can wildcard the version portion. Say the example below was Dell Command | Configure 3.1, you could use Dell Command | Configure for $Application and it would still find the app. You might wonder why I am outputting the name of the application. All SCCM wants to see is a string output which it interprets as installed. If no output occurs, then SCCM interprets that as not installed. 


 $Application = 'Dell Command | Configure'  
$InstalledApps = Get-ChildItem -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse | ForEach-Object {$_.GetValue('DisplayName')}
If (@($InstalledApps) -like ('*' + $Application + '*')) {
Write-Host (@($InstalledApps) -like $Application)
}
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Recently, I have been implementing new SCCM compliance policies for enhanced security. Some of the policies on my list will require the Dell Command | Update to ensure systems have up-to-date drivers and BIOS versions installed.

This blog entry has been written with the assumption that you already know how to create a compliance item and baseline. I am skipping all of the setup processes and getting right to the discovery, compliance rule, and remediation. 

I set up the policy to use a PowerShell script that returns a boolean value if Dell Command | Update is installed. 



The discovery portion uses the following PowerShell script. Luckily, Dell does not change the name of the application in Programs and Features. 

 $InstalledApps = Get-ChildItem -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall", "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" -Recurse | ForEach-Object {$_.GetValue('DisplayName')}  
If ($InstalledApps -contains 'Dell Command | Update') {
echo $true
} else {
echo $false
}




Here is the remediation script used to install Dell Command | Update if it is missing. You will need to change the <UNC Path> to the UNC path where the installer is located.

 $Executable = "<UNC Path>\DCU_Setup_2_4_0.exe"  
$Switches = '/S /v/qb-'
$ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode




Finally, here is the compliance rule page that verifies if the application is installed. 

  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
While working on a new compliance policy, I ran into a lot of hurdles that needed to be resolved. One of those hurdles was executing an SCCM package via PowerShell. Using the WMIExplorer, I was able to locate a method that allows you to execute an SCCM package as shown below.


Once I located the namespace, class, and method, I needed to find out the name of the package in SCCM that I wanted to execute. To do this, the easiest method is to perform a WMI query in PowerShell on the advertised machine. The query is:

 Get-WmiObject -Class ccm_program -Namespace root\ccm\ClientSDK  

The results will display all advertised programs to that machine. From the results, locate the package you are wanting to execute by the Name, which will match the name in the SCCM console. Once you have found the package, take note of the PackageID and ProgramID, as these are the two items needed to execute the package via PowerShell.

The syntax of calling this from PowerShell is as follows, where the Program ID and Package ID are substituted with the appropriate data from the WMI query:

 ([wmiclass]'root\ccm\ClientSDK:CCM_ProgramsManager').ExecuteProgram('<ProgramID>','<Package ID>')  

The following PowerShell command line method will allow you to call this from the command line, where the Program ID and Package ID are substituted with the appropriate data from the WMI query:

 powershell.exe -executionpolicy bypass -command "&{([wmiclass]'root\ccm\ClientSDK:CCM_ProgramsManager').ExecuteProgram('<ProgramID>','<Package ID>')}"  

When executing the package, there will be output. If you allow for the default output, it can take an extended period of time to gather the information. Specifying a specific field will dramatically speed up the execution time.

NOTE: One thing I learned after discovering this is that SCCM Compliance rules cannot execute this WMI method. I will be writing a separate blog on that in the future as I just finished the compliance policy that goes into detail on executing an SCCM package. 
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Recently, I have been working on Configuration Baselines for security purposes. While doing so, two of my baselines required remediation that takes longer than 1 minute. I do not recall where I read it, but I believe the timeout for a compliance check is 1 minute. If the compliance remediation takes longer than 1 minute, then the baseline is designated as non-compliant until the next compliance check is run. This snippet of code can also be used in any other instance where the configuration manager client is installed.

To expedite this process, I tracked down how to execute a compliance check through PowerShell so that it can be executed at the end of the remediation script. Thanks to Trevor Sullivan's blog post, I was able to grab and modify the code from it to make into an easy to use code snippet within a Baseline remediation PowerShell script.

To make this easier, I wrote the script as two lines. The first line is where you specify the name of the baseline. As you can see in the pic below of a partial list of baseline configurations, the names of those baselines are what you need to specify for the variable $Name. The code snippet at the bottom shows using the Pending Reboot name to trigger a compliance check for that baseline. 



Once you have specified the name of the baseline, you can then copy and paste both lines at the bottom of the PowerShell remediation script so that a baseline configuration is triggered at the end of the remediation. Here is the code snippet: 


 $Name='Pending Reboot'  
([wmiclass]"root\ccm\dcm:SMS_DesiredConfiguration").TriggerEvaluation(((Get-WmiObject -Namespace root\ccm\dcm -class SMS_DesiredConfiguration | Where-Object {$_.DisplayName -eq $Name}).Name), ((Get-WmiObject -Namespace root\ccm\dcm -class SMS_DesiredConfiguration | Where-Object {$_.DisplayName -eq $Name}).Version))
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 
Recently, we had an issue of some machines not backing up the Bitlocker recovery password to active directory, even with the GPO in place. They ended up being offline while the bitlocker process took place. Plus, some of the systems in AD had multiple entries, which can be cumbersome. To mitigate this issue, I have implemented an SCCM Configuration Baseline that makes sure the Bitlocker recovery password is backed up to AD and that it is the only recovery password present.

To do this, I first implemented a baseline that enabled the RSAT active directory feature in Windows 10. This is needed so the scripts can query and write to AD. Once this was deployed, I created the BitLocker Recovery Password Backup configuration item.


 Platforms must be set to Windows 10 as some of the cmdlets used in the scripts only exist in that OS and newer.



The script returns a true or false value that dictates if remediation is needed.


The first script queries the local system and AD for the recovery passwords to compare. If they match and only one is in AD, then True is returned that dictates the system is in compliance. False is returned if there is no password stored in AD, there is more than one password in AD, or the wrong password is stored in AD.

Here is the discovery script:

 $RecoveryKey = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'}  
$ADBitLockerRecoveryKey = (Get-ADObject -Filter {objectclass -eq 'msFVE-RecoveryInformation'} -SearchBase (Get-ADComputer -Identity $env:COMPUTERNAME).DistinguishedName -Properties 'msFVE-RecoveryPassword')
If ($ADBitLockerRecoveryKey -eq $null) {
Echo $false
} elseif ($ADBitLockerRecoveryKey -isnot [system.Array]) {
If (([string]$RecoveryKey.RecoveryPassword).Trim() -eq ([string]$ADBitLockerRecoveryKey.'msFVE-RecoveryPassword').Trim()) {
Echo $true
} else {
Echo $false
}
} elseif ($ADBitLockerRecoveryKey -is [system.Array]) {
Echo $false
}


Next comes the remediation script. This is what will be executed if the discovery script returns a False value:



 $RecoveryKey = (Get-BitLockerVolume -MountPoint $env:SystemDrive).KeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'}  
Write-Host 'Local Recovery Password:'$RecoveryKey.RecoveryPassword
$ADBitLockerRecoveryKey = (Get-ADObject -Filter {objectclass -eq 'msFVE-RecoveryInformation'} -SearchBase (Get-ADComputer -Identity $env:COMPUTERNAME).DistinguishedName -Properties 'msFVE-RecoveryPassword')
Write-Host ' AD Recovery Password:'$ADBitLockerRecoveryKey.'msFVE-RecoveryPassword'
If (($ADBitLockerRecoveryKey -isnot [system.Array]) -and ($ADBitLockerRecoveryKey -ne $null)) {
Remove-ADObject -Identity $ADBitLockerRecoveryKey.DistinguishedName -Confirm:$false
} elseif ($ADBitLockerRecoveryKey -is [system.Array]) {
Foreach ($Key in $ADBitLockerRecoveryKey) {
Write-Host 'Removing'$Key.DistinguishedName
Remove-ADObject -Identity $Key.DistinguishedName -Confirm:$false
}
}
Backup-BitLockerKeyProtector -MountPoint $env:SystemDrive -KeyProtectorId $RecoveryKey.KeyProtectorId


The final thing to set in the configuration item is the compliance rule as shown below:


Now that the configuration item is created, the configuration baseline must be created and deployed. Here are the screenshots of my configuration baseline that I later deployed out to all laptop systems, which are the systems here that are BitLockered.




Read for later

Articles marked as Favorite are saved for later viewing.
close
  • Show original
  • .
  • Share
  • .
  • Favorite
  • .
  • Email
  • .
  • Add Tags 

Separate tags by commas
To access this feature, please upgrade your account.
Start your free month
Free Preview