Prepare SCCM Clients for Cloning

Annoying task… configuring VMware View desktops for use in an environment that utilizes VMware View.  Some say, don’t put the management agent on the View desktop, just rebuild your desktops every time there is a security patch.  I say, even if recomposing your pools is fast and easy, I still do not want to do it with every patch release.

Best practice for preparing a reference computer for deployment in an SCCM environment is to not include the SCCM client.  However, SCCM client installation is SLOW, so I would like to save time and CPU load by including the software.  Documentation how to do this is sketchy.  Also, I really want a script fired off by the QuickPrep process to do the prep work, so that someone does not forget that it needs to be done.

Here is my first pass at the script… again, not too pretty, but functional:

'==========================================================================
'
'  NAME:    sccmClientPrep.vbs
'
'  AUTHOR:  J. Greg Mackinnon
'  DATE:    2013-02-01
'
'  COMMENT: prepares SCCM client for cloning
'           Requires:
'             - Certutil.exe in %systemroot%system32 (included with Win7)
'           Returns:
'           1 - CCMEXEC service stop failure
'           2 - Machine Certificate Store deletion failure
'           4 - SMS Certificate Store deletion failure
'           8 - SMSCFG.INI deltion failure
'==========================================================================
option explicit

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        CONSTANTS
const MACH_STORE = "My"
const SMS_STORE = "SMS"
const SVCNAME = "ccmexec"
const TIMEOUT = "120"

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        OBJECTS
dim oShell
set oShell = CreateObject("WScript.Shell")

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        VARIABLES
dim sSysRoot,sCUPath,sINIPath
dim iRet, iExit

sSysRoot = oShell.ExpandEnvironmentStrings("%SystemRoot%")
sCUPath = sSysRoot & "system32certutil.exe"
sINIPath = sSysRoot & "SMSCFG.INI"
iExit = 0

'=-=-=-=-=-=-=-=-=-=-=-=-=
'   FUNCTIONS AND SUBS
function stopSvc(sSvcName,iTimeout)
' Stops the Windows service with name matching input string "sSvcName".
' Times out in iTimeout seconds.
' Needs routine to verify that sSvcName is a valid service name.
	'Variables:
	dim bDone 
	dim iSecs 
	bDone = False
	iSecs = 0

	'Objects and Collections:
	dim cSvcs
	dim oWMI, oSvc
	Set oWMI = GetObject("winmgmts:{impersonationLevel=impersonate}!\.rootcimv2")
	Set cSvcs = oWMI.ExecQuery("Select * from Win32_Service Where Name = '" & sSvcName & "'")

	'Stop the service if it is running, exit if it is not running
	For Each oSvc In cSvcs
		if oSvc.State = "Running" then
			oSvc.StopService
		else
			stopSvc = 0
			exit function
		end if
		exit for 'Only on service in collection
	Next

	'Check on the service until stopped.  Timeout in iTimeout seconds.
	Do while bDone = False
		Set cSvcs = oWMI.ExecQuery("Select * from Win32_Service Where Name = '" & sSvcName & "'")
		bDone = True
		For Each oSvc In cSvcs
			If oSvc.State  "Stopped" Then
				bDone = False
				WScript.Sleep 1000
				Exit For
			End If
		Next
		iSecs = iSecs + 1
		If iSecs >= iTimeout Then
			stopSvc = 1
			exit function
		End If
	Loop 
	stopSvc = 0
end function

function delCert(sStore,sSerial)
'Deletes certificate in certificate store "sStore" with serial number "sSerial"
'Returns: The ExitCode from certutil.exe
'Requires: 
'   - WScript.Shell object named "oShell"
'   - Defined path to "certutil.exe" named "sCUPath"
'   - Presence of certutil.exe on the system
	dim oExec, oStdOut
	dim sLine
	
	'wscript.echo "About to execute: " & sCUPath & " -delstore " & sStore & " " & sSerial
	set oExec = oShell.Exec(sCUPath & " -delstore " & sStore & " " & sSerial)
	Set oStdOut = oExec.StdOut
	Do While oExec.Status = 0
		WScript.Sleep 100
	Loop
	'Uncomment the next four lines to debug certutil:
	'Do Until oStdOut.AtEndOfStream
	'	sLine = oStdOut.ReadLine
	'	wscript.echo sLine
	'Loop
	delCert = oExec.ExitCode
end function

function getCert(sStore)
'Gets the serial numbers of certificates in the machine store specified by "sStore"
'Sends the captured serial numbers to the "delCert" function for deletion.
'Returns: 1 - If cert deletion files, 0 - If no errors are detected.
'Requires: 
'   - WScript.Shell object named "oShell"
'   - Defined path to "certutil.exe" named "sCUPath"
'   - Presence of certutil.exe on the system
	dim oExec, oStdOut
	dim bFail
	dim i, iRet
	dim sLine, sSerial
	
	bFail=False
	Set oExec = oShell.Exec(sCUPath & " -store " & sStore)
	Set oStdOut = oExec.StdOut
	
	Do Until oStdOut.AtEndOfStream
		sLine = oStdOut.ReadLine
		if InStr(1,sLine,"Serial Number",1) then
			i = CInt(InStr(1,sLine,":",1) + 2)
			sSerial = Mid(sLine,i)
			iRet = delCert(sStore,sSerial)
			if (iRet  0) then
				wscript.echo "Certificate deletion failed"
				bFail = True
			end if
		end if
	Loop
	
	if bFail = True then
		getCert = 1
	else
		getCert = 0
	end if
end function

function delFile(sFile)
'Deletes the file specified by "sFile"
'Requires existing Wscript.Shell object named "oShell"
	dim oFSO, oFile
	set oFSO = CreateObject("Scripting.FileSystemObject") 

	'wscript.echo "About to delete file: " & sFile
	if oFSO.FileExists(sFile) then
		'Delete method will force a WSH quit if it fails, so we need to disable exit-on-error:
		Err.Clear
		On Error Resume Next
		
		set oFile = oFSO.GetFile(sFile)
		delFile = oFile.Delete(True)
		if Err.Number = 0 then
			delFile = 0
		else
			delFile = Err.Number
		end if
	else
		'Exit code for the function could be changed here if you are concerned about
		' the requested file to delete not being present on the system.
		'wscript.echo "File " & sFile & " does not exist."
		delFile = 0
	end if
end function

'=-=-=-=-=-=-=-=-=-=-=-=-=
'          MAIN
iRet = stopSvc(SVCNAME, TIMEOUT)
'wscript.echo "Return from stopSvc: " & iRet
if iRet  0 then
	iExit = iExit + 1
end if

iRet = getCert(MACH_STORE)
'wscript.echo "Return from cert deletion for store " & MACH_STORE & ": " & iRet
if iRet  0 then
	iExit = iExit + 2
end if

iRet = getCert(SMS_STORE)
'wscript.echo "Return from cert deletion for store " & SMS_STORE & ": " & iRet
if iRet  0 then
	iExit = iExit + 4
end if

iRet = delFile(sINIPath)
'wscript.echo "Return from file deletion: " & iRet
if iRet  0 then
	iExit = iExit + 8
end if

WScript.Quit iExit

Scriptomatic Access to the Start Menu and Taskbar

As promised in my previous post, here is my current VBScript for configuring the Windows 7 Start Menu and Taskbar. Not beautiful, but certainly functional. My thanks to JuliusPIV and cogumel0 for doing the heavy lifting that made this script possible.

Note that you really will need to set the Group Policy option to turn off the Start Menu program history if you want Start Menu pinning to be at all effective in streamlining the Windows 7 “first time” GUI.

'==========================================================================
'
' NAME: Pin & Unpin items to/from Start Menu & Taskbar
'
' AUTHOR: J. Greg Mackinnon
' DATE  : 2013-01-31
'
' COMMENT: Derived from code by JuliusPIV found here:
'   http://social.technet.microsoft.com/Forums/en/w7itproinstall/thread/73eb1c0a-fc78-4ae7-ba6d-356d9a9a5328
'
'   To add items to Start Menu or Taskbar, add a variable defining the 
'   path to the original link in the variables section, then add that 
'   variable to the "aPinSM", "aPinTB", or "aUnpinTB" arrays.
'
'   Note that not all links (such as filesystem shortcuts) can be pinned.
'
'   Uncomment "debugecho" lines to troubleshoot.
'
'==========================================================================
option explicit

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        CONSTANTS
'=-=-=-=-=-=-=-=-=-=-=-=-=
'List of "Shell Special Folder Constants" used in the script.  See:
'http://msdn.microsoft.com/en-us/library/windows/desktop/bb774096(v=vs.85).aspx
const ssfAPPDATA = &H1a
const ssfCOMMONPROGRAMS = &H17
const ssfPROGRAMFILESx86 = &H30
const ssfPROGRAMS = &H2 
const ssfSYSTEM = &H25
const ssfWINDOWS = &H24

'=-=-=-=-=-=-=-=-=-=-=-=-=
'         OBJECTS
'=-=-=-=-=-=-=-=-=-=-=-=-=
dim fso, oShell, oShortcut
set fso = CreateObject("Scripting.FileSystemObject")
set oShell = CreateObject("Shell.Application") 

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        VARIABLES
'=-=-=-=-=-=-=-=-=-=-=-=-=
dim aPinSM, aPinTB, aUnpinTB
dim bEchoOut, bPinItem
dim sAUP, sUP, sRAD, sPFx86, sSys32, sItem, sScriptHost, sFileName
dim sGC, sMOW, sMOE, sMOPP, sMOON, sMOO, sOC, sFZ, sPT, sProj, sCalc, sSnip, sPDN, sMag, sKey, sWMP

'Configure variables for well known folders:
sRAD = oShell.NameSpace(ssfAPPDATA).Self.Path & ""            'Roaming AppData
sAUP = oShell.NameSpace(ssfCOMMONPROGRAMS).Self.Path & ""     'Start Menu Programs - All Users
sUP = oShell.NameSpace(ssfPROGRAMS).Self.Path & ""            'Start Menu Programs - Current User
'sPFx86 = oShell.NameSpace(ssfPROGRAMFILESx86).Self.Path & "" 'Program Files (x86)
'sSys32 = oShell.NameSpace(ssfSYSTEM).Self.Path & ""          '%WinDir%system32

'List of links to be added to the Start Menu or Taskbar, relative to:
' C:ProgramDataMicrosoftWindowsStart MenuPrograms
sGC = sAUP & "Google ChromeGoogle Chrome.lnk"
sMOW = sAUP & "Microsoft Office 2013Word 2013.lnk"
sMOE = sAUP & "Microsoft Office 2013Excel 2013.lnk"
sMOPP = sAUP & "Microsoft Office 2013PowerPoint 2013.lnk"
sMOON = sAUP & "Microsoft Office 2013Onenote 2013.lnk"
sMOO = sAUP & "Microsoft Office 2013Outlook 2013.lnk"
sOC = sAUP & "Oracle CalendarOracle Calendar.lnk"
sFZ = sAUP & "FileZilla FTP ClientFileZilla.lnk"
sPT = sAUP & "PuTTYPuTTY.lnk"
sProj = sAUP & "Accessoriesdisplayswitch.lnk"
sCalc = sAUP & "AccessoriesCalculator.lnk"
sSnip = sAUP & "AccessoriesSnipping Tool.lnk"
sPDN = sAUP & "Paint.NET.lnk"
sMag = sUP & "AccessoriesAccessibilityMagnify.lnk"
sKey = sUP & "AccessoriesAccessibilityOn-Screen Keyboard.lnk"
sWMP = sRAD & "MicrosoftInternet ExplorerQuick LaunchUser PinnedTaskBarWindows Media Player.lnk"

'Arrays containing links to be added to StartMenu or Taskbar, or to be removed from the Taskbar:
aPinSM = Array(sOC,sFZ,sPT,sPDN,sSnip,sCalc,sProj,sMag,sKey)
aPinTB = Array(sGC,sMOW,sMOE,sMOPP,sMOON)
aUnpinTB = Array(sWMP,sMOW,sMOPP)

'=-=-=-=-=-=-=-=-=-=-=-=-=
'   FUNCTIONS AND SUBS
'=-=-=-=-=-=-=-=-=-=-=-=-=
function PinSM(shortcut)
	dim oFolder, oFolderItem
	dim sFolder, sFile
	dim colVerbs
	dim itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Pinning " & sFolder & "" & sFile & " to Start Menu."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs

	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Pin to Start Menu" then itemverb.DoIt
	next
end function

function PinTB(shortcut)
	dim sFolder, sFile
	dim oFolder, oFolderItem
	dim colVerbs, itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Pinning " & sFolder & "" & sFile & " to Taskbar."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs
	
	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Pin to Taskbar" then itemverb.DoIt
	next
end function

function UnpinTB(shortcut)
	dim sFolder, sFile
	dim oFolder, oFolderItem
	dim colVerbs, itemverb
	
	sFolder = fso.GetParentFolderName(shortcut)
	sFile = fso.GetFileName(shortcut)

	'debugecho "Unpinning " & sFolder & "" & sFile & " from Taskbar."
	Err.Clear
					
	set oFolder = oShell.NameSpace(sFolder)
	set oFolderItem = oFolder.ParseName(sFile)
	set colVerbs = oFolderItem.Verbs
	
	for each itemverb in oFolderItem.Verbs
		if Replace(itemverb.name, "&", "") = "Unpin from Taskbar" then itemverb.DoIt
	next
end function

function debugecho(msg)
	if bEchoOut then
		wscript.echo msg
	end if
end function

sub Main
	for each sItem in aUnpinTB
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to unpin does not exist."
			'debugecho "Please check the input and try again."
		else
			UnpinTB(sItem)
		end if
	next
	for each sItem in aPinSM
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to pin does not exist."
			'debugecho "Please check the input and try again."
		else
			PinSM(sItem)
		end if
	next
	for each sItem in aPinTB
		if not fso.FileExists(sItem) then
			bPinItem = false
			'debugecho "File, " & sItem & ", to pin does not exist."
			'debugecho "Please check the input and try again."
		else
			PinTB(sItem)
		end if
	next
end sub

'=-=-=-=-=-=-=-=-=-=-=-=-=
'        MAIN BODY
'=-=-=-=-=-=-=-=-=-=-=-=-=
'Suppress echo if we are in WScript:
sScriptHost = LCase(Wscript.FullName)
if Right(sScriptHost, 11) = "wscript.exe" then
    bEchoOut = false
else
    bEchoOut = true
end if

call Main