Tuesday, March 30, 2021

SCCM/MEM ContentLibraryCleanUp.exe Automation

Purpose:

To help others take what I have done and use for their own environment with modifications or to improve upon what I have done for others to use in the future.  Use at own risk.

Problem:

The SCCMContentLib was growing beyond what it should.  Orphaned package data was being left behind.  This was happening on many of our Distribution Points and began throwing errors for low disk space.  Found the Microsoft provided ContentLibraryCleanup.exe but it was a very manual process to run across our enterprise.

Solution:

I created a couple scripts that could either be ran locally on the DP or remotely from a machine that had the SCCM/MEM Admin Console installed.  These scripts were written with a hierarchy of a single primary site server.  This will not run on a Primary Site Server with DP role.

Prerequisites: (Only 1 script is needed)

ContentLibraryCleanup-Local.ps1 – Run manually on DP or create a package and call within a Task Sequence to automate. (FAST)

ContentLibraryCleanup-Remote.ps1– Run manually from a remote machine or setup as a scheduled task to automate. (SLOW)

ContentLibraryCleanup.exe – Find in CD.Latest\SMSSETUP\TOOLS\ContentLibraryCleanup on the site server

How it works:

ContentLibraryCleanup-Local.ps1: Place script and contentlibrarycleanup.exe in the same folder.  Run the script from powershell.  The script will gather the FQDN of the machine and start logging.  It will start running in WhatIF mode and if successful it will run in Delete mode.  The logs from the script can be found at c:\windows\logs\contentlibrarycleanup\ and the logs from ContentLibraryCleanup.exe can be found in the folder where the script and exe reside.

To automate it further I created a Package in SCCM with the script and contentlibrarycleanup.exe in the same folder.  No Program was required.  Then I created a Task Sequence with a single task of Run PowerShell Script.  I pointed this task at the package, script name: ContentLibraryCleanup-local.ps1, Execution Policy: Bypass, and selected run this step as the following account and provided credentials that had Admin rights locally as well as to the SCCM environment. Once the TS was created I deployed it to a collection that only had Distribution Points in it.

 

ContentLibraryCleanup-Remote.ps1: Place script and contentlibrarycleanup.exe in the same folder.  The SCCM console must reside on the same machine.  Run the script from powershell.  The script will ask you for your site code and the primary site server.  It will then gather all the Distribution Points in the environment and start logging.  It will start running in WhatIF mode and if successful it will run in Delete mode for each DP it finds.  The logs from the script can be found at c:\windows\logs\contentlibrarycleanup\ and the logs from ContentLibraryCleanup.exe can be found in the folder where the script and exe reside.

To automate it further you could change the prompts for site code and primary site server to parameters and then feed the script the parameters and set it as a scheduled task that will run with admin rights to the remote DPs as well as SCCM.

Additional Info:

Content library cleanup tool - Configuration Manager | Microsoft Docs

Tuesday, June 30, 2020

Change/Update SCCM-MEM Certificates

Change/Update SCCM-MEM Certificates

Purpose:

To help others take what I have done and use for their own environment with modifications or to improve upon what I have done for others to use in the future.  Use at own risk.

Problem:

We were moving to a new PKI environment with new CA and SubCAs.  We needed to update our certificates on our SCCM/MEM infrastructure servers.  This included client authentication, distribution point, management point, and software update point certificates.  Our environment had 80+ DPs, 2 MPs, and 1 Primary Site Server.

Solution:

WinRM was already enabled.  We pushed the new Root CA and SubCA to the infrastructure servers using GPO then created a few scripts to automate the needed tasks to update the certificates.  This included the certificate request, export, IIS binding, and importing the certificate into the console for DPs.  This took our change/update tasks from multiple hours to change over to ~45 min and almost all of which was just script runtime and just monitoring the console output.

Scripts:

Script 1 – Enable CredSSP on infrastructure servers

Script 2 – Certificate request, export, and bind IIS certificate

Script 3 – Import DP certificate into the console.

Modifications:

All areas that need to be modified are in “< >” symbols

Script 2

1.      Production - Line 6, 9, 15, 21, 33

2.      Testing - Line 97-103

3.      Error/Resume - Line 116

Script 3

1.      Production - Line 45, 50

Wednesday, February 5, 2020

Modern Driver Management - Task Sequence

Modern Driver Management – Task Sequence

Problem

I wanted to update my drivers only once and have it update all my task sequences that use drivers.  Usually each of my task sequences for operating system deployments or operating system upgrades had their own driver sets in the sequences.

Solution

I created was a new task sequence that would hold all the drivers for all our known hardware models.  This TS would be called from our deployment or upgrade task sequences.  This sequence will utilize a TS variable to identify if the hardware model is known or not and Download Package Content tasks. You will need a separate Driver Package or Package with the driver files for each hardware model.  You will also need an “empty” Driver Package or Package so the OS Deployment and OS Upgrade sequences can work correctly.  In your empty package you will need to place something in it as it will not want to replicate unless it has something in it.  Either a small text file if you made a package or a small driver if it is a Driver Package.  Keep reading to see how this works. 

Inspiration

The below link seemed like it made this a lot harder to implement.  I did not try it so I cannot speak to the difficulty, but it was a lot of info. https://deploymentresearch.com/configmgr-driver-management-in-just-four-steps-by-matthew-teegarden/

Process

Create the new driver task sequence

1.      Create two task sequence variable tasks called OSDKnownModels

a.      The first TS variable should have a value of NO

b.      The second TS variable should have a value of YES and Options tab should have WMI queries

2.      The Known PC Model Upgrades group should have a condition of TS Variable OSDKnownModels = YES

3.      Each Driver model needs to be setup as a Download Package Content task along with a wmi condition for the driver model under the Known PC Model Group.  It also needs to have the following options selected

a.      Place into the following location: Task sequence working directory

b.      Check – Save path as a variable: Driver Pack

4.      Unknown PC Models group needs to have a TS Variable condition of OSDKnownModels = NO

5.      Place your Unknown Model driver package or package in the Unknown PC Models group.  This package does not need to have any conditions.  It also needs to have the following options selected

a.      Place into the following location: Task sequence working directory

b.      Check – Save path as a variable: Driver Pack

Operating System Upgrade Task Sequence

1.      Your OS Upgrade Task needs to have two tasks to work successfully.

2.      Create a new Run Task Sequence task and browse to your Model Drivers Task Sequence

3.      Create a new Upgrade Operating System task

a.      Select your Upgrade Package and Edition

b.      Check – Provide the following driver content to Windows Setup during upgrade

                                                    i.     Select Staged content: type %DriverPack01%

1.      DriverPack is the variable from the driver TS but 01 is because the driver was the 1st package in the Download Package Content task.

Operating System Deployment Task Sequence

1.      After the Apply Operating System Task and while still in Windows PE add a new task

2.      Create a Run Task Sequence task and browse to the Drivers Task Sequence you created above

3.      Create a Run Command Line task to inject the drivers into the Windows Install

a.      Command line should be: DISM.exe /Image:%OSDISK%\ /Add-Driver /Driver:%DriverPack01%\ /Recurse

b.      %OSDISK% is the variable we use in our Format tasks as well as Apply Operating System tasks

c.      %DriverPack01% is the variable that is created from the Driver Task Sequence

Conclusion

Please ask if there are any questions.  I will try to answer as time permits.

Wednesday, June 14, 2017

ConfigMgr Current Branch Custom Task Sequence Variable for Computer Role

ConfigMgr Current Branch Custom Task Sequence Variable for Computer Role

Problem

I needed a way to identify the type or role a machine was going to be within the PE portion of an OSD sequence.  The initial thought was to read the computer name with wmi and use the like variable to control different actions within the task sequence.  This would have worked but I needed to get this information within Windows PE and the computer name would typically be MININT-xxxxxx.  You could call the task sequence variable _SMSTSMachineName or OSDComputerName but a task sequence variable can only be equal or not equal.  The like variable is not allowed on any task sequence variable.

Resolution

I created a PowerShell script that will create a custom OSD Task Sequence Variable called OSDComputerRole.  This script is set to run after the computer naming script that I created in a previous post found here.  You can download the previous posts scripts and files from here.  This new script is set to run after the OSDComputerName variable is set.  The new script also uses the same csv file that the naming script utilized which is explained in the previous post.  The OSDComputerRole allows for the computer role to be read from the csv file and then you can call the task sequence variable as a condition to the task sequence step.  I hope this helps someone else in their search to do something similar.  The code to the PowerShell script is below and can be downloaded from here.

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment

 

$ComputerName = $tsenv.Value('OSDComputerName')

$csv = Import-Csv .\Win10OUMap.csv -delimiter ';'

    foreach ($line in $csv) { 

        IF ($computername -Match $line.NAME){

            $tsenv.Value('OSDComputerRole') = $line.Role

                                           Exit

            }

        Else {

            $tsenv.Value('OSDComputerRole') = 'Unknown'

            }

    }

 

 

Tuesday, July 19, 2016

ConfigMgr 2012 Name Computer During OSD Task Sequence

ConfigMgr 2012 Name Computer During OSD Task Sequence

Problem

I needed a way to prompt for computer name during the OSD sequence.  I found an old post by texasmcse found here that got me started.  The second part that was needed was to get some validation around the name that is input so it would follow our naming standards.

Resolution

 

I created a new script with parts of code from the post above to prompt for a site code, then prompt for computer role, and finally it takes the last 7 characters of the serial to form the computer name.  It uses a csv file to validate the site code and role are correct before moving on.  If there is an incorrect configuration it will respond and start over.  It will check the computer name that is stored in the task sequence variable _SMSTSMachineName.  If the variable is set to MININT it will prompt for computer naming.  If it is set to something else, it will utilize what ConfigMgr is passing it.  I hope this is useful to others.  I am sure the code could be cleaner but it works well. In the code the CSV file is named Win10OUMap.csv.  The Win10OUMap.csv file has 5 columns in the following order; Name,OU,BusinessUnit,SiteCode,Role.  This is a ; delimited file.  The Name column is what is used to validate the name of the computer as it will contain the site and role format to ensure it is valid.  When you modify the script file remember that when you import a CSV file in VB the first column is 0 not 1.  Again hope this helps.

Code:

'On Error Resume Next

Set WshShell = WScript.CreateObject("WScript.Shell")

Set NetworkObject = CreateObject("WScript.Network")

Set objFSO = CreateObject("Scripting.FileSystemObject")

set SCCMenv = CreateObject("Microsoft.SMS.TSEnvironment")

 

'Find if known in SCCM

NameLookup = SCCMenv("_SMSTSMachineName")

'NameLookup = "MININT-234343"

SearchChar = "MININT"

'msgbox NameLookup

StrComputer = "."

IF inStr(NameLookup , SearchChar) Then

              SiteCodeMatch = 10

              SiteRoleMatch = 10

              PCNameMatch = 10

              Validated = False

                             'Get Computer Serial Number

              Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")

              Set colSMBIOS = objWMIService.ExecQuery ("Select * from Win32_SystemEnclosure")

              For Each objSMBIOS in colSMBIOS

              SNStr = objSMBIOS.SerialNumber

              SNShort = RIGHT(SNStr,7)

                             'msgbox snshort

 

              Do While answer <> vbYes

                             Do While Validated = False

                                           Do while SiteCodeMatch < 20

                                                          set SiteCodeFile = objFSO.OpenTextFile("Win10OUMap.csv")

 

                                                          SiteCode = InputBox ("Enter the 4 Character Site Code" & vbCrLf & "I.e. CORP=Corporate, OLME=Mesa, INDY=Indianapolis etc..", "Prompt for Site Code:",,0,0)

                                                          SiteCode = UCase(SiteCode)

                                                          'msgbox SiteCode

                                                                        Do while NOT SiteCodeFile.AtEndOfStream

                                                                                      arrStrSiteCode = split(sitecodefile.ReadLine,";")

                                                                                       strSiteCode = arrStrSiteCode(3)

                                                                                      IF SiteCode = strSiteCode Then

                                                                                      'msgbox "EQUALS " & strSiteCode & " " & SiteCode

                                                                                      'msgbox "I Match"

                                                                                      SiteCodeMatch = 20

                                                                                      Exit Do

                                                                                      Else

                                                                                      'msgbox "Not Equal " & strSiteCode & " " & SiteCode

                                                                                      'msgbox "I dont match"

                                                                                      End IF

                                                                        loop

                                                                        SiteCodeFile.Close

                                                                        IF SiteCodeMatch < 20 Then

                                                                        MsgBox  "Invalid Site Code!  Site Codes are defined as a 4 Character Location, I.e. CORP=Corporate, OLME=Mesa OpenLane, INDY=Indianapolis, BOST=Boston, OLTO=Toronto Openlane etc.. If you do not know your site code please contact the ServiceDesk.", 69680

                                                                        Else

                                                                        End IF

                                           Loop

 

                                           'wscript.echo SiteCode

                                           'wscript.echo SiteCodeMatch

 

                                           Do while RoleCodeMatch < 20

                                                          set RoleCodeFile = objFSO.OpenTextFile("Win10OUMap.csv")

 

                                                          RoleCode = InputBox ("Enter the Single Character function role of the Computer," & vbCrLf & "i.e. W=Workstation L=Laptop K=Kiosk, B=Block, D=Digital Arb etc...", "Enter the Computer Role for " & sitecode & ":",,0,0)

                                                          RoleCode = UCase(RoleCode)

                                                          'msgbox RoleCode

                                                                        Do while NOT RoleCodeFile.AtEndOfStream

                                                                                      arrStrRoleCode = split(RoleCodefile.ReadLine,";")

                                                                                      strRoleCode = arrStrRoleCode(4)

                                                                                      IF RoleCode = strRoleCode Then

                                                                                      'msgbox "EQUALS " & strRoleCode & " " & RoleCode

                                                                                      'msgbox "I Match"

                                                                                      RoleCodeMatch = 20

                                                                                      Exit Do

                                                                                      Else

                                                                                      'msgbox "Not Equal " & strRoleCode & " " & RoleCode

                                                                                      'msgbox "I dont match"

                                                                                      End IF

                                                                        loop

                                                                        RoleCodeFile.Close

                                                                        IF RoleCodeMatch < 20 Then

                                                                        MsgBox  "Invalid Role Code!  Roles are Defined as a single character for the function of the Computer i.e. W=Workstation L=Laptop, K=Kiosk, B=Block, D=Digital Arb etc.. If you do not know the Function codes for site " & SiteCode & " please contact the ServiceDesk.", 69680

                                                                        Else

                                                                        End IF

                                           Loop

 

                                           'wscript.echo RoleCode

                                           'wscript.echo RoleCodeMatch

                                           PCName = SiteCode & "-" & RoleCode & "-" & SNShort

                                           set PCNameFile = objFSO.OpenTextFile("Win10OUMap.csv")

                                           Do while NOT PCNameFile.AtEndOfStream

                                                          arrStrPCName = split(PCNamefile.ReadLine,";")

                                                          strPCName = arrStrPCName(0)

                                                          IF inStr(PCName , strPCName) Then

                                                          'msgbox "EQUALS " & strPCName & " " & PCName

                                                          'msgbox "I Match"

                                                          PCNameMatch = 20

                             '                            Exit Do

                                                          Else

                                                          'msgbox "Not Equal " & strPCName & " " & PCName

                                                          'msgbox "I dont match"

                                                          End IF

                                           loop

                                           PCNameFile.Close

                                           IF PCNameMatch < 20 Then

                                           MsgBox  "PC Name is Invalid!  PC Name does not meet the standards in the PC Name to OU Map file.  If you do not know the naming standards for the site " & SiteCode & " please contact the ServiceDesk.", 69680

                                           Validated = False

                                           SiteCodeMatch = 10

                                           RoleCodeMatch = 10

                                           Else

                                           'MsgBox "PCNameValid"

                                           Validated = True

                                           End IF

                            

                             Loop

                                           If PCNameMatch = 20 Then

                                                          answer = MsgBox("Are you sure this correct?" & vbCrLf & PCName, 69668, "Verify Name")

                                                          IF answer = vbNo then

                                                                        SiteCodeMatch = 10

                                                                        RoleCodeMatch = 10

                                                                        Validated = False

                                                          Else

                                                         

                                                          PCNameFinal = PCName

                                                          'msgbox PCNameFinal

                                                          SCCMenv("OSDComputerName") = PCNameFinal

                                                          End If

                                           Else

                                           answer = vbNo

                                           End IF

              loop

              Next

Else

'msgbox "Did not match"

End IF

Friday, July 8, 2016

PowerShell failing in a ConfigMgr 2012 OSD Task Sequence

PowerShell failing in a ConfigMgr 2012 OSD Task Sequence

Problem

We had a PowerShell script trying to be executed from a Command Line task within our OSD Task Sequence.  This was failing with either running the command manually or calling it from within the package within the OSD Task Sequence.  What was odd and troublesome is if I ran the same commands within its own Custom Task Sequence outside of the OSD Task Sequence it would work.  The error that was seen in the smsts.log file was The term XXX is not recognized as the name of a cmdlets, function, script file, or operable program.  Check the spelling of the name, or if a path was included, verify the path is correct and try again.”  This error was found for almost every PowerShell action that the script was performing.

Resolution

I came across a post by Mike Griswold found here that explained part of the issue.  The issue is that when PowerShell is called within an OSD Task Sequence and within the OS (NOT PE) it does not have any modules loaded like default.  What I had to do was from the OS open PowerShell and run my script.  After the script completed I ran Get-Module from within the same PowerShell window.  This step was important as when running Get-Module by default from a fresh PowerShell window it will not give you all the modules the script referenced. After running Get-Module from within the same PowerShell window after the script ran it gave me the Modules that the script required.  I then modified my PowerShell script that I was using within my SCCM package to include the needed modules.  The specific lines I added to my .PS1 file are listed below.  I added the -verbose so I could see within the SMSTS.log file that the modules were in fact importing correctly.  After this modification everything started working as expected and my sanity returned.

import-module -Name C:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Management -verbose

import-module -Name C:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Security -verbose

import-module -Name C:\windows\system32\WindowsPowerShell\v1.0\Modules\Microsoft.PowerShell.Utility -verbose

Tuesday, April 19, 2016

PowerShell: Disable, Move, Delete AD Computer Objects

PowerShell: Disable, Move, Delete AD Computer Objects

Problem

Stale computer objects being left behind in an Active Directory environment. 

Solution

This PowerShell script will resolve the problem.  It was written to search a specific OU and sub OU’s to find stale records.  Stale records are defined in the script as a computer that has not changed its password in greater than 4 months and has also not logged into the domain in greater than 4 months.  Both are configurable in the script.  It will then disable the computer, move it to a specified OU, and tag the description with the date that it was disabled for future reference.  I will go into detail on usage below.

Download Script

Download

CommandLine Options

This script must be called from the PowerShell shell.  It has 4 switches to enable different portions of the script.  By default, I have turned on –WhatIF on any actions that may cause issue in an environment.  –Force turns off –WhatIF on those commands as shown below   

-MoveAlreadyDisabled will MOVE already Disabled computers in a specified source OU ($DisableSourceOU) to a specified target OU ($DisableTargetOU).

-DisableAndMove will MOVE and DISABLE Enabled Computers in a specified source OU ($DisableSourceOU) to a specified target OU ($DisableTargetOU) based on criteria.  (Read Notes area)

-DeleteDisabled will DELETE already Disabled computers based on criteria and OU. ($DisableTargetOU) (Read Notes area)

-Force Will Turn Off -WhatIF where -WhatIF is defined (-WhatIF:$True is on by Default)

 

As each section is turned on they will create output files in C:\Scripts

 

Parts of the Script to Modify

$DisableSourceOU – This is the OU you want the script to Scan and look for stale records

$DisableTargetOU – This is the OU you want the script to place the stale OU

Change the (-x) after AddMonths to your desired time from current date (-4 goes in the past 4 months if you want to go in the future remove the - sign . 

1.       $DisableComputers = get-adcomputer -properties * -filter {enabled -eq "True"} -SearchBase $DisableSourceOU -SearchScope Subtree | where {($_.lastLogonDate -lt (get-date).AddMonths(-4)) -and ($_.passwordlastset -lt (get-date).AddMonths(-4)) }

2.       $DeleteComputers = get-adcomputer -properties * -filter {enabled -eq "False"} -searchbase $DisableTargetOU -SearchScope Subtree  | where { $_.Modified -lt (get-date).AddMonths(-1) }