SCCM Reboot DECODED:: How to make a PC Cancel, Start, Extend or Change mandatory reboot to non-mandatory on the fly.

, , , , , , , , ,

How to make a PC Cancel, Start, Extend or Change mandatory reboot to non-mandatory on the fly.

In the past to stop a PC from rebooting when you didn’t want it to people would stop the ccmexec service and do a shutdown /a

But here is the problem with that..

  1.  Shutdown /a will normally tell you that no shutdown is pending.
  2.  Any number of things can restart ccmexec.

First, how to check if a reboot is pending VS a reboot is GOING to happen

On a win10 PC or has powershell 5 installed, use this

#Detect pending reboot:
Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending

On a win7 or old powershell 2 you need to use these.

#
([wmiclass]'ROOTccmClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().RebootPending
([wmiclass]'ROOTccmClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().IsHardRebootPending
([wmiclass]'ROOTccmClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().RebootDeadline
([wmiclass]'ROOTccmClientSDK:CCM_ClientUtilities').DetermineIfRebootPending().NotifyUI

It will spit out something like this..

#PC with no pending reboot.

DisableHideTime     : 12/31/1969 2:00:00 PM
InGracePeriod       : False
IsHardRebootPending : False
NotifyUI            : False
RebootDeadline      : 12/31/1969 2:00:00 PM
RebootPending       : False
ReturnValue         : 0
PSComputerName      :

#PC with pending NON-mandatory reboot.

DisableHideTime : 12/31/1969 12:00:00 PM
InGracePeriod : False
IsHardRebootPending : False
NotifyUI : True
RebootDeadline : 12/31/1969 12:00:00 PM
RebootPending : True
ReturnValue : 0
PSComputerName :

#PC with pending mandatory reboot, notice the time stamp.

DisableHideTime : 5/1/2019 3:28:56 PM
InGracePeriod : True
IsHardRebootPending : False
NotifyUI : True
RebootDeadline : 5/1/2019 11:28:56 PM
RebootPending : True
ReturnValue : 0
PSComputerName :

 

Now for the magic…..

Everything that SCCM uses for knowing when and if it should reboot comes from here.

HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData

On a PC with no reboot pending, this key is empty.

So starting with that, this is how we can CANCEL a pending reboot.

#CANCEL a pending reboot
Remove-Item -path 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData';
Remove-Item -path 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Updates Management\Handler\UpdatesRebootStatus\*';
Remove-ItemProperty -name * -path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired';

#on PS2.0, "Remove-ItemProperty" doesn't work, so use this.
#Remove-Item -path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired';

shutdown -a 
Restart-Service ccmexec -force

But what if you just want to cancel the mandatory reboot and change it to a non-mandatory reboot so the user will still get the popup telling them they “need to” reboot?

#change mandatory reboot to  non-mandatory reboot
Set-Itemproperty -path 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -name 'RebootBy' -value 0;
Restart-Service ccmexec -force

What if we just want to extend the time of a mandatory reboot?

Example: In your client settings you have your reboot countdown set to 10 hours…. A user calls and says it’s going to reboot in 10 min and needs it extended… This will reset that users countdown timer back to 10 hours.

#Reset SCCM reboot countdown timer.
$time = [DateTimeOffset]::Now.ToUnixTimeSeconds()
Set-Itemproperty -path 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -name 'RebootBy' -value $time;
Restart-Service ccmexec -force

What if you want to kick off the built in SCCM reboot WITH the client settings countdown timer?
NOTE: Setting $Time to 0 will popup the non-mandatory reboot window.

$time = [DateTimeOffset]::Now.ToUnixTimeSeconds()
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'RebootBy' -Value $time -PropertyType QWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'RebootValueInUTC' -Value 1 -PropertyType DWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'NotifyUI' -Value 1 -PropertyType DWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'HardReboot' -Value 0 -PropertyType DWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'OverrideRebootWindowTime' -Value 0 -PropertyType QWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'OverrideRebootWindow' -Value 0 -PropertyType DWord -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'PreferredRebootWindowTypes' -Value @("4") -PropertyType MultiString -Force -ea SilentlyContinue;
New-ItemProperty -LiteralPath 'HKLM:\SOFTWARE\Microsoft\SMS\Mobile Client\Reboot Management\RebootData' -Name 'GraceSeconds' -Value 0 -PropertyType DWord -Force -ea SilentlyContinue;

 

NOTE:: In all my tests after you run The powerShell command it takes about 30 seconds for the client to respond since after you restart the service it has to do all it’s internal checks to figure out what’s new.

#WeaponizedAutismFTW

Archiving AD Accounts with PowerShell

, , , ,

Problem:

I work for a small K-12 school district. Often when teachers retire, they return as substitutes. We are also required maintain public records for seven years. For these reasons, we do not delete staff accounts immediately after their employment ends.

I had a process for archiving employees and activating old accounts, but it was time consuming. I also wanted to ensure that procedures were followed by new staff members.  A few of the steps I take when archiving an account are, setting Logon Workstation and Logon Hours, adding the account to a “Archived Users” group, removing all other groups, and, of course, disabling the account.

I knew I could automate this with PowerShell, but I am late to game and didn’t really know where to start. I have adapted scripts for my needs, but I have not done a project like this from start to finish. In addition to automating some account management, I wanted to expand some PowerShell skills.

Solution:

I was lucky enough to attend Ignite 2018 and caught Tools, tips and tricks from the SysAdmin field by my friend, Harjit (@hoorge). Towards the end of the session, around 17:20, he shows Active Directory Admin Center (ADAC) and the awesome Windows PowerShell History Viewer. For those unfamiliar with this feature, every action that you do in ADAC generates PowerShell code.

Active Directory Administrative Center

Copy and paste the code into PowerShell ISE and string each step of your process together. You will end up with a script like the one below:

Set-ADUser -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -LogonWorkstations:"No-PC-for-You" -Replace:@{"logonHours"="0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0","0"}
Add-ADPrincipalGroupMembership -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -MemberOf:"CN=Archived Users,OU=Security Groups,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com"
Set-ADObject -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -Replace:@{'primaryGroupID'="2103"}
Remove-ADPrincipalGroupMembership -Confirm:$false -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com" -MemberOf:"CN=Domain Users,CN=Users,DC=corp,DC=viamonstra,DC=com"
Set-ADAccountExpiration -DateTime:"12/29/2018 00:00:00" -Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com"

Not only do we have a starting point for a User Management script, but we are also getting a solid introduction to PowerShell and how it works. Taking this a step further, let’s look at how to replace the -Identity parameter with a variable. To accomplish this, I created two variables for the User Account, $Account and $AccountDetails. $Account asks for the user name. $AccountDetails uses Get-AdUser to gather details of the Account, specifically DistinguishedName.

$Account = Read-Host -Prompt 'Input the user name'
$AccountDetails = Get-ADUser $Account

At this point, we can now replace

-Identity:"CN=Mike,OU=Users,OU=ViaMonstra,DC=corp,DC=viamonstra,DC=com"

With

-Identity:$AccountDetails.DistinguishedName 

This small addition greatly increases the usefulness of this script. Since Distinguished Name contains the full path, I do not need to worry about typos in OU names. Also, I can hand this script off to help desk staff to archive accounts without worrying that a step was missed. Below is the full script that I currently use when archiving accounts. You will notice that I added several more variables, $DenyHours, $ArchivedUsersGroup, and $DisableOU. I used Richard Siddaway’s blog post, https://richardspowershellblog.wordpress.com/2012/01/26/setting-a-users-logon-hours/ to declare $DenyHours. $ArchivedUsers and $DisabledOU are variables that I declared for my group and OU for disabled accounts.

#Declare Logon Hours
[byte[]]$DenyHours = @(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0)
#Declare OUs
$DisableOU = "OU=- Disabled Accounts,OU=Users,DC=ViaMonstra,DC=org"
#Declare Groups
$ArchivedUsersGroup = "CN=Archived Users,OU=Groups,DC=ViaMonstra,DC=org"
# Input Account and get Account details
$Account = Read-Host -Prompt 'Input the user name'
$AccountDetails = Get-ADUser $Account
#Disable Account
Disable-ADAccount -Identity:$AccountDetails.DistinguishedName
# Set Logon Restrictions
Set-ADUser -Identity:$AccountDetails.DistinguishedName -LogonWorkstations:"No-PC-for-You" -Replace:@{logonHours=$DenyHours
# Add Archive Flag for Email Autoreply Rules
Set-ADUser -Identity:$AccountDetails.DistinguishedName -Office:"Archive"
# Add Account to 'Archived Users' group and Set as primary
Add-ADGroupMember -Identity:$ArchivedUsersGroup -Members:$AccountDetails
Set-ADObject -Identity:$AccountDetails -Replace:@{'primaryGroupID'="ChangeToYourGroupID"}
# Set Description to Disabled Date
Set-ADUser $AccountDetails -Description "Account Disabled on $(Get-Date -format 'd')"
# Remove From all the Groups
Get-ADGroup -Filter {name -notlike "*Archived Users*"}  | Remove-ADGroupMember -Members $AccountDetails.samaccountname -Confirm:$False 
# Move Account to Disabled Users OU
Move-ADObject -Identity:$AccountDetails.DistinguishedName -TargetPath:$DisableOU

To recap, I defined a procedure to archive accounts. Using ADAC, I captured the PowerShell commands for each step. Finally, I replaced static information such as, Account Name, OU’s, and Groups with variables. I hope that this gives you some ideas for managing user accounts with PowerShell in your environment.

Update – I added a screenshot of ADAC, changed the disable account to the cleaner “Disable-ADAccount” and fixed a couple of typos.