It was time to upgrade the old SCCM Client Installer. I wanted to add new features and also make the script be able to execute by using parameters. With the help of Sapien's PowerShell Studio, this was very easy to do! I did not include all optional fields, but for my own company, this works great and you can easily modify it if there are additional parameters of the client installer you need in your environment.
The first thing I did was to make the parameters available for the client installer directory, client installer filename, management point, SMS site code, and two switches defining whether to install or uninstall. I centered this script around using the ccmsetup.exe file to both install and uninstall. Another feature I added is the Build parameter. This parameter can be added with the install parameter, or by itself. This configures the SCCM client in preparation for a sysprep of the computer. This is primarily done before sysprepping a machine using SCCM or MDT that captures a reference image. This allows for the client to be already present when the reference image is laid down on a newly imaged computer. It then communicates back to the SCCM server to configure itself as a new client when that reference image is laid down on new machines. I have included command line examples within the script's documentation.
This is a video of the script executing to uninstall the SCCM client on a machine.
This video shows the script installing the SCCM client. You can see that I include the uninstallation of the application first so that if this is being run to fix a machine, it will first uninstall and then install the client. Another thing you will see is that it initializes the installation and then goes on to wait for the installation to complete. I put the waiting in there because once the client begins its install, it closes out the first instance of the ccmsetup.exe and opens up a new one, thereby making the script think the installation finished, when in essence, it did not. I make the script wait because I want to make sure that when the install takes place, it is completely finished when the script closes. When the script executes during a build, especially during a reference image build, it needs to time complete the setup before the sysprep. That is the purpose of the wait. If you do not want it to wait, you can disable lines 264 and 280.
Finally, below is a pic of the script being executed to prepare the SCCM client for a sysprep. I have also included a pop-up window in the event the SCCM client is not properly prepared for the sysprep. The window will stay there until OK is clicked. This gives the admin the capability of either manually deleting the file and registry keys and stopping the ccmexec service, or restarting the build.
You can download the script from here.
1: <#
2: .SYNOPSIS
3: Install SCCM Client
4:
5: .DESCRIPTION
6: Uninstall any old client and install the new SCCM client
7:
8: .PARAMETER Build
9: Select if this is being executed while building a reference image
10:
11: .PARAMETER ClientInstallationDirectory
12: Directory where the client ClientInstallationFile is located
13:
14: .PARAMETER ClientInstallationFile
15: SCCM ClientInstallationFile
16:
17: .PARAMETER Install
18: A description of the Install parameter.
19:
20: .PARAMETER ManagementPoint
21: SCCM Management Point
22:
23: .PARAMETER SMSSiteCode
24: SMS Site Code
25:
26: .PARAMETER Uninstall
27: A description of the Uninstall parameter.
28:
29: .PARAMETER UsePKICert
30: Specifies whether clients use PKI certificate when available
31:
32: .PARAMETER NOCRLCheck
33: Specifies that clients do not check the certificate revocation list (CRL) for site systems
34:
35: .PARAMETER Source
36: Specifies the source location from which to download installation files. This can be a local or UNC path.
37:
38: .EXAMPLE
39: New installation
40: powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Install
41:
42: Uninstall
43: powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Uninstall
44:
45: SCCM/MDT/Sysprep
46: powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Build
47: powershell.exe -executionpolicy bypass -file SCCMClient.ps1 -Install -Build
48:
49: .NOTES
50: The above examples do not include the $ClientInstallationDirectory and
51: the $ClientInsallationFile. I prepopulated the data within the parameter
52: definitions below. I also define the $ManagementPoint and $SMSSiteCode. I
53: have not tested the $UsePKICert, $NOCRLCheck, or $Source fields as we do
54: not use those where I work, therefore I cannot verify if they are valid.
55:
56: ===========================================================================
57: Created with: SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127
58: Created on: 8/2/2016 2:50 PM
59: Created by: Mick Pletcher
60: Organization:
61: Filename: SCCMClientInstaller.ps1
62: ===========================================================================
63:
64: #>
65: [CmdletBinding()]
66: param
67: (
68: [switch]
69: $Build,
70: [ValidateNotNullOrEmpty()][string]
71: $ClientInstallationDirectory = '',
72: [ValidateNotNullOrEmpty()][string]
73: $ClientInstallationFile = 'ccmsetup.exe',
74: [switch]
75: $Install,
76: [string]
77: $ManagementPoint = '',
78: [string]
79: $SMSSiteCode = '',
80: [switch]
81: $Uninstall,
82: [switch]
83: $UsePKICert,
84: [switch]
85: $NOCRLCheck,
86: [string]
87: $Source
88: )
89:
90:
91: function Get-MetaData {
92: <#
93: .SYNOPSIS
94: Get File MetaData
95:
96: .DESCRIPTION
97: A detailed description of the Get-MetaData function.
98:
99: .PARAMETER FileName
100: Name of File
101:
102: .EXAMPLE
103: PS C:\> Get-MetaData -FileName 'Value1'
104:
105: .NOTES
106: Additional information about the function.
107: #>
108:
109: [CmdletBinding()][OutputType([object])]
110: param
111: (
112: [ValidateNotNullOrEmpty()][string]
113: $FileName
114: )
115:
116: Write-Host "Retrieving File Description Data....." -NoNewline
117: $MetaDataObject = New-Object System.Object
118: $shell = New-Object -COMObject Shell.Application
119: $folder = Split-Path $FileName
120: $file = Split-Path $FileName -Leaf
121: $shellfolder = $shell.Namespace($folder)
122: $shellfile = $shellfolder.ParseName($file)
123: $MetaDataProperties = 0..287 | Foreach-Object { '{0} = {1}' -f $_, $shellfolder.GetDetailsOf($null, $_) }
124: For ($i = 0; $i -le 287; $i++) {
125: $Property = ($MetaDataProperties[$i].split("="))[1].Trim()
126: $Property = (Get-Culture).TextInfo.ToTitleCase($Property).Replace(' ', '')
127: $Value = $shellfolder.GetDetailsOf($shellfile, $i)
128: If ($Property -eq 'Attributes') {
129: switch ($Value) {
130: 'A' {
131: $Value = 'Archive (A)'
132: }
133: 'D' {
134: $Value = 'Directory (D)'
135: }
136: 'H' {
137: $Value = 'Hidden (H)'
138: }
139: 'L' {
140: $Value = 'Symlink (L)'
141: }
142: 'R' {
143: $Value = 'Read-Only (R)'
144: }
145: 'S' {
146: $Value = 'System (S)'
147: }
148: }
149: }
150: #Do not add metadata fields which have no information
151: If (($Value -ne $null) -and ($Value -ne '')) {
152: $MetaDataObject | Add-Member -MemberType NoteProperty -Name $Property -Value $Value
153: }
154: }
155: [string]$FileVersionInfo = (Get-ItemProperty $FileName).VersionInfo
156: $SplitInfo = $FileVersionInfo.Split([char]13)
157: foreach ($Item in $SplitInfo) {
158: $Property = $Item.Split(":").Trim()
159: switch ($Property[0]) {
160: "InternalName" {
161: $MetaDataObject | Add-Member -MemberType NoteProperty -Name InternalName -Value $Property[1]
162: }
163: "OriginalFileName" {
164: $MetaDataObject | Add-Member -MemberType NoteProperty -Name OriginalFileName -Value $Property[1]
165: }
166: "Product" {
167: $MetaDataObject | Add-Member -MemberType NoteProperty -Name Product -Value $Property[1]
168: }
169: "Debug" {
170: $MetaDataObject | Add-Member -MemberType NoteProperty -Name Debug -Value $Property[1]
171: }
172: "Patched" {
173: $MetaDataObject | Add-Member -MemberType NoteProperty -Name Patched -Value $Property[1]
174: }
175: "PreRelease" {
176: $MetaDataObject | Add-Member -MemberType NoteProperty -Name PreRelease -Value $Property[1]
177: }
178: "PrivateBuild" {
179: $MetaDataObject | Add-Member -MemberType NoteProperty -Name PrivateBuild -Value $Property[1]
180: }
181: "SpecialBuild" {
182: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SpecialBuild -Value $Property[1]
183: }
184: }
185: }
186:
187: #Check if file is read-only
188: $ReadOnly = (Get-ChildItem $FileName) | Select-Object IsReadOnly
189: $MetaDataObject | Add-Member -MemberType NoteProperty -Name ReadOnly -Value $ReadOnly.IsReadOnly
190: #Get digital file signature information
191: $DigitalSignature = get-authenticodesignature -filepath $FileName
192: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateSubject -Value $DigitalSignature.SignerCertificate.Subject
193: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateIssuer -Value $DigitalSignature.SignerCertificate.Issuer
194: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateSerialNumber -Value $DigitalSignature.SignerCertificate.SerialNumber
195: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateNotBefore -Value $DigitalSignature.SignerCertificate.NotBefore
196: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateNotAfter -Value $DigitalSignature.SignerCertificate.NotAfter
197: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureCertificateThumbprint -Value $DigitalSignature.SignerCertificate.Thumbprint
198: $MetaDataObject | Add-Member -MemberType NoteProperty -Name SignatureStatus -Value $DigitalSignature.Status
199: If (($MetaDataObject -ne "") -and ($MetaDataObject -ne $null)) {
200: Write-Host "Success" -ForegroundColor Yellow
201: } else {
202: Write-Host "Failed" -ForegroundColor Red
203: }
204: Return $MetaDataObject
205: }
206:
207: function Invoke-EXE {
208: <#
209: .SYNOPSIS
210: Install or Uninstall Executable
211:
212: .DESCRIPTION
213: A detailed description of the Invoke-EXE function.
214:
215: .PARAMETER InstallerMetaData
216: The metadata extracted from the executable
217:
218: .PARAMETER Install
219: Specify to Install the application
220:
221: .PARAMETER Uninstall
222: Specify to uninstall the application
223:
224: .PARAMETER Executable
225: The installation file for installing the application
226:
227: .PARAMETER Switches
228: Switches to control the executable file
229:
230: .PARAMETER DisplayName
231: Name to be displayed while installing or uninstalling the application
232:
233: .EXAMPLE
234: PS C:\> Invoke-EXE
235:
236: .NOTES
237: Additional information about the function.
238: #>
239:
240: [CmdletBinding()][OutputType([boolean])]
241: param
242: (
243: [object]
244: $InstallerMetaData,
245: [switch]
246: $Install,
247: [switch]
248: $Uninstall,
249: [ValidateNotNullOrEmpty()][string]
250: $Executable,
251: [string]
252: $Switches,
253: [string]
254: $DisplayName
255: )
256:
257: If ($Install.IsPresent) {
258: Write-Host "Initiating Installation of"$DisplayName"....." -NoNewline
259: $File = $env:windir + "\ccmsetup\ccmsetup.exe"
260: $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode
261: If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {
262: Write-Host "Success" -ForegroundColor Yellow
263: If ((Test-Path $File) -eq $true) {
264: Wait-ProcessEnd -ProcessName ccmsetup
265: } else {
266: Write-Host "Failed" -ForegroundColor Red
267: $Failed = $true
268: }
269: } else {
270: Write-Host "Failed with error"$ErrCode -ForegroundColor Red
271: }
272: } elseif ($Uninstall.IsPresent) {
273: Write-Host "Uninstalling"$DisplayName"....." -NoNewline
274: $File = $env:windir + "\ccmsetup\ccmsetup.exe"
275: If ((Test-Path $File) -eq $true) {
276: $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -WindowStyle Minimized -Wait -Passthru).ExitCode
277: If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {
278: Write-Host "Success" -ForegroundColor Yellow
279: If ((Test-Path $File) -eq $true) {
280: Wait-ProcessEnd -ProcessName ccmsetup
281: }
282: } else {
283: $Failed = $true
284: Write-Host "Failed with error"$ErrCode -ForegroundColor Red
285: }
286: } else {
287: Write-Host "Not Present" -ForegroundColor Green
288: }
289: }
290: If ($Failed -eq $true) {
291: Return $false
292: } else {
293: Return $true
294: }
295: }
296:
297: function Remove-File {
298: <#
299: .SYNOPSIS
300: Delete a file with verification
301:
302: .DESCRIPTION
303: Delete a file and verify the file no longer exists
304:
305: .PARAMETER Filename
306: Name of the file to delete
307:
308: .EXAMPLE
309: PS C:\> Remove-File -Filename 'Value1'
310:
311: .NOTES
312: Additional information about the function.
313: #>
314:
315: [CmdletBinding()][OutputType([boolean])]
316: param
317: (
318: [ValidateNotNullOrEmpty()][string]
319: $Filename
320: )
321:
322: If ((Test-Path $Filename) -eq $false) {
323: Write-Host $Filename" already deleted"
324: } else {
325: $File = Get-Item $Filename -Force
326: Write-Host "Deleting"$File.Name"....." -NoNewline
327: If (Test-Path $File) {
328: Remove-Item $File -Force -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null
329: If ((Test-Path $Filename) -eq $False) {
330: Write-Host "Success" -ForegroundColor Yellow
331: } else {
332: $Failed = $true
333: Write-Host "Failed" -ForegroundColor Red
334: }
335: } else {
336: Write-Host "Not Present" -ForegroundColor Green
337: }
338: }
339: If ($Failed -eq $true) {
340: Return $false
341: } else {
342: Return $true
343: }
344: }
345:
346: function Remove-RegistryKey {
347: <#
348: .SYNOPSIS
349: Delete registry key
350:
351: .DESCRIPTION
352: Delete a registry key. If recurse is selected, all subkeys and values are deleted
353:
354: .PARAMETER RegistryKey
355: Registry key to delete
356:
357: .PARAMETER Recurse
358: Include all subkeys when deleting the registry key
359:
360: .EXAMPLE
361: PS C:\> Remove-RegistryKey -RegistryKey 'Value1'
362:
363: .NOTES
364: Additional information about the function.
365: #>
366:
367: [CmdletBinding()]
368: param
369: (
370: [ValidateNotNullOrEmpty()][string]
371: $RegistryKey,
372: [switch]
373: $Recurse
374: )
375:
376: $RegKey = "Registry::" + $RegistryKey
377: If ((Test-Path $RegKey) -eq $false) {
378: Write-Host $RegKey" already deleted"
379: } else {
380: $RegKeyItem = Get-Item $RegKey
381: If ($Recurse.IsPresent) {
382: Write-Host "Recursive Deletion of"$RegKeyItem.PSChildName"....." -NoNewline
383: Remove-Item $RegKey -Recurse -Force | Out-Null
384: } else {
385: Write-Host "Deleting"$RegKeyItem.PSChildName"....." -NoNewline
386: Remove-Item $RegKey -Force | Out-Null
387: }
388: If ((Test-Path $RegKey) -eq $false) {
389: Write-Host "Success" -ForegroundColor Yellow
390: } else {
391: $Failed = $true
392: Write-Host "Failed" -ForegroundColor Red
393: }
394: }
395: If ($Failed -eq $true) {
396: Return $false
397: } else {
398: Return $true
399: }
400: }
401:
402: function Set-ConsoleTitle {
403: <#
404: .SYNOPSIS
405: Console Title
406:
407: .DESCRIPTION
408: Sets the title of the PowerShell Console
409:
410: .PARAMETER ConsoleTitle
411: Title of the PowerShell Console
412:
413: .NOTES
414: Additional information about the function.
415: #>
416:
417: [CmdletBinding()]
418: param
419: (
420: [Parameter(Mandatory = $true)][String]
421: $ConsoleTitle
422: )
423:
424: $host.ui.RawUI.WindowTitle = $ConsoleTitle
425: }
426:
427: function Suspend-Service {
428: <#
429: .SYNOPSIS
430: Stop specified service
431:
432: .DESCRIPTION
433: Stop a specified service and verify it is stopped
434:
435: .PARAMETER Service
436: Name of the service
437:
438: .EXAMPLE
439: PS C:\> Suspend-Service -Service 'Value1'
440:
441: .NOTES
442: Additional information about the function.
443: #>
444:
445: [CmdletBinding()][OutputType([boolean])]
446: param
447: (
448: [ValidateNotNullOrEmpty()][string]
449: $Service
450: )
451:
452: $ServiceStatus = Get-Service $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
453: If ($ServiceStatus -ne $null) {
454: Write-Host "Stopping"$ServiceStatus.DisplayName"....." -NoNewline
455: If ($ServiceStatus.Status -ne 'Stopped') {
456: Stop-Service -Name $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -Force
457: $ServiceStatus = Get-Service $Service -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
458: If ($ServiceStatus.Status -eq 'Stopped') {
459: Write-Host "Success" -ForegroundColor Yellow
460: } else {
461: $Failed = $true
462: Write-Host "Failed" -ForegroundColor Red
463: }
464: } else {
465: Write-Host "Service already stopped" -ForegroundColor Yellow
466: }
467: } else {
468: Write-Host $Service"service does not exist"
469: }
470: If ($Failed -eq $true) {
471: Return $false
472: } else {
473: Return $true
474: }
475: }
476:
477: function Wait-ProcessEnd {
478: <#
479: .SYNOPSIS
480: Wait for a process to end
481:
482: .DESCRIPTION
483: Pause the script until a process no longer exists
484:
485: .PARAMETER ProcessName
486: Name of the process
487:
488: .EXAMPLE
489: PS C:\> Wait-ProcessEnd -ProcessName 'Value1'
490:
491: .NOTES
492: Additional information about the function.
493: #>
494:
495: [CmdletBinding()]
496: param
497: (
498: [ValidateNotNullOrEmpty()][string]
499: $ProcessName
500: )
501:
502: $Process = Get-Process $ProcessName -ErrorAction SilentlyContinue
503: $Process = $Process | Where-Object { $_.ProcessName -eq $ProcessName }
504: Write-Host "Waiting for"$Process.Product"to complete....." -NoNewline
505: If ($Process -ne $null) {
506: Do {
507: Start-Sleep -Seconds 2
508: $Process = Get-Process $ProcessName -ErrorAction SilentlyContinue
509: $Process = $Process | Where-Object { $_.ProcessName -eq $ProcessName }
510: }
511: While ($Process -ne $null)
512: Write-Host "Completed" -ForegroundColor Yellow
513: } else {
514: Write-Host "Process already completed" -ForegroundColor Yellow
515: }
516: }
517:
518: cls
519: #Set the name of the powershell console
520: Set-ConsoleTitle -ConsoleTitle "SCCM Client"
521: #Skip over if the install directory and installer file are not defined
522: If ($ClientInstallationDirectory -ne $null) {
523: If ($ClientInstallationFile -ne $null) {
524: If ($ClientInstallationDirectory[$ClientInstallationDirectory.Length - 1] -ne '\') {
525: $ClientInstallationDirectory += '\'
526: }
527: #Set the location and filename of the SCCM client installer
528: $File = $ClientInstallationDirectory + $ClientInstallationFile
529: #Get metadata from the SCCM client installer file
530: $FileMetaData = Get-MetaData -FileName $File
531: }
532: }
533: #Install parameter is defined
534: If ($Install.IsPresent) {
535: #Uninstall the SCCM client
536: $Parameters = "/uninstall"
537: $InstallStatus = Invoke-EXE -Uninstall -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters
538: If ($InstallStatus = $false) {
539: $Failed = $true
540: }
541: #Install the SCCM client
542: $Parameters = ""
543: If (($ManagementPoint -ne $null) -and ($ManagementPoint -ne "")) {
544: $Parameters += "/mp:" + $ManagementPoint
545: }
546: If (($SMSSiteCode -ne $null) -and ($SMSSiteCode -ne "")) {
547: If ($Parameters -ne "") {
548: $Parameters += [char]32
549: }
550: $Parameters += "SMSSITECODE=" + $SMSSiteCode
551: }
552: If ($UsePKICert.IsPresent) {
553: If ($Parameters -ne "") {
554: $Parameters += [char]32
555: }
556: $Parameters += "/UsePKICert"
557: }
558: If ($NOCRLCheck.IsPresent) {
559: If ($Parameters -ne "") {
560: $Parameters += [char]32
561: }
562: $Parameters += "/NOCRLCheck"
563: }
564: If (($Source -ne $null) -and ($Source -ne "")) {
565: If ($Parameters -ne "") {
566: $Parameters += [char]32
567: }
568: $Parameters += "/source:" + [char]34 + $Source + [char]34
569: }
570: $InstallStatus = Invoke-EXE -Install -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters
571: If ($InstallStatus -eq $false) {
572: $Failed = $true
573: }
574: #Uninstall parameter is defined
575: } elseif ($Uninstall.IsPresent) {
576: #Uninstall the SCCM client
577: $Parameters = "/Uninstall"
578: $InstallStatus = Invoke-EXE -Uninstall -DisplayName $FileMetaData.Product -Executable $File -Switches $Parameters
579: If ($InstallStatus -eq $false) {
580: $Failed = $true
581: }
582: }
583: #Build parameter is defined
584: If ($Build.IsPresent) {
585: #Stop the configuration manager client service
586: $InstallStatus = Suspend-Service -Service ccmexec
587: If ($InstallStatus -eq $false) {
588: $Failed = $true
589: }
590: #Delete the smscfg.ini file
591: $InstallStatus = Remove-File -Filename $env:windir"\smscfg.ini"
592: If ($InstallStatus -eq $false) {
593: $Failed = $true
594: }
595: #Delete the SCCM certificates from the registry
596: $InstallStatus = Remove-RegistryKey -RegistryKey "HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates\SMS\Certificates" -Recurse
597: If ($InstallStatus -eq $false) {
598: $Failed = $true
599: }
600: }
601: If ($Failed -eq $true) {
602: $wshell = New-Object -ComObject Wscript.Shell
603: $wshell.Popup("Installation Failed", 0, "Installation Failed", 0x0)
604: Exit 1
605: } else {
606: Exit 0
607: }
608:
0 comments:
Post a Comment