25 August 2015

Missing Bitlocker Recovery Keys Reporting Tool

In a perfect environment, GPO forces the bitlocker recovery keys into AD. This doesn't always function correctly. Sometimes a system is bitlockered offline and the key isn't there. Other times, a system might be decrypted and they forget to re-encrypt it. This script is written to generate a report on all systems that have not reported a bitlocker recovery key to AD. If a firm has MBAM, then this is likely not needed.

This script will query SCCM for a list of laptop systems. It will then query active directory for a list of bitlockered systems. The script then compares the laptop list with the bitlockered systems list and generates an excel report of systems which do not have a bitlocker key stored in active directory. This can happen when a system is manually encrypted. This script must be executed on the SCCM server in order for this to execute. It has to load the SCCM module, which can only be done from the server. The $OutputFile and $Path are used to specify where to save the excel report and what to name the file.

This can be either run manually, or you can implement this to run from Orchestrator on a scheduled basis. I wrote the script so that if all bitlockered systems have the recovery key present in AD, then an excel report is not generated. Orchestrator looks for the excel spreadsheet. If it is not present, then it does nothing. If it is present, then it emails that spreadsheet to the appropriate management.

NOTE: In order for this script to function, you will need to have the powershell command line active directory enabled on the SCCM server.

The only changes you should have to make are the following:

  • Line 38 if you want to prepopulate the location for saving the csv file
  • Line 56 to set the site server code for your SCCM server
  • Line 136 to point to wherever your SCCM module resides

You can download the script from here.


1:  <#       
2:       .NOTES  
3:       ===========================================================================  
4:        Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.92  
5:        Created on:       8/25/2015 1:25 PM  
6:        Created by:       Mick Pletcher  
7:        Organization:        
8:        Filename:        MissingBitlockerKeys.ps1  
9:       ===========================================================================  
10:       .DESCRIPTION  
11:            This script will query SCCM for a list of laptop systems. It will then  
12:      query active directory for a list of bitlockered systems. The script   
13:      then compares the laptop list with the bitlockered systems list and  
14:      generates an excel report of systems which do not have a bitlocker  
15:      key stored in active directory. This can happen when a system is   
16:      manually encrypted. This script must be executed on the SCCM server  
17:      in order for this to execute. It has to load the SCCM module, which  
18:      can only be done from the server. The $OutputFile and $Path are used  
19:      to specify where to save the excel report and what to name the file.  
20:    
21:      In order for this script to function, you will need to have the   
22:      powershell command line active directory enabled.   
23:        
24:      This can be either run manually, or you can implement this to run  
25:      from Orchestrator on a scheduled basis. I wrote the script so that  
26:      if all bitlockered systems have the recovery key present in AD, then  
27:      an excel report is not generated. Orchestrator looks for the excel  
28:      spreadsheet. If it is not present, then it does nothing. If it is  
29:      present, then it emails that spreadsheet to the appropriate   
30:      management.  
31:  #>  
32:    
33:  param  
34:  (  
35:       [string]  
36:       $OutputFile = 'MissingBitlockerKeys.csv',  
37:       [string]  
38:       $Path  
39:  )  
40:    
41:  function ProcessTextFile {  
42:       If ((Test-Path -Path $OutputFile) -eq $true) {  
43:            Remove-Item -Path $OutputFile -Force  
44:       }  
45:  }  
46:    
47:    
48:  function Get-Laptops {  
49:       #Declare Local Variables  
50:       Set-Variable -Name Item -Scope Local -Force  
51:       Set-Variable -Name QuerySystems -Scope Local -Force  
52:       Set-Variable -Name Systems -Scope Local -Force  
53:       Set-Variable -Name WQL -Scope Local -Force  
54:         
55:       $QuerySystems = @()  
56:       Set-Location SiteServerCode:  
57:       $WQL = 'select SMS_R_SYSTEM.ResourceID,SMS_R_SYSTEM.ResourceType,SMS_R_SYSTEM.Name,SMS_R_SYSTEM.SMSUniqueIdentifier,SMS_R_SYSTEM.ResourceDomainORWorkgroup,SMS_R_SYSTEM.Client from SMS_R_System inner join SMS_G_System_SYSTEM_ENCLOSURE on SMS_G_System_SYSTEM_ENCLOSURE.ResourceId = SMS_R_System.ResourceId where SMS_G_System_SYSTEM_ENCLOSURE.ChassisTypes = "8" or SMS_G_System_SYSTEM_ENCLOSURE.ChassisTypes = "9" or SMS_G_System_SYSTEM_ENCLOSURE.ChassisTypes = "10" or SMS_G_System_SYSTEM_ENCLOSURE.ChassisTypes = "14"'  
58:       $Systems = Get-WmiObject -Namespace Root\SMS\Site_BNA -Query $WQL  
59:       Foreach ($Item in $Systems) {  
60:            $QuerySystems = $QuerySystems + $Item.Name  
61:       }  
62:       Set-Location c:  
63:       $QuerySystems = $QuerySystems | Sort-Object  
64:    Return $QuerySystems  
65:         
66:       #Cleanup Local Variables  
67:       Remove-Variable -Name Item -Scope Local -Force  
68:       Remove-Variable -Name QuerySystems -Scope Local -Force  
69:       Remove-Variable -Name Systems -Scope Local -Force  
70:       Remove-Variable -Name WQL -Scope Local -Force  
71:  }  
72:    
73:  Function Get-BitlockeredSystems {  
74:    #Declare Local Variables  
75:    Set-Variable -Name BitLockerObjects -Scope Local -Force  
76:    Set-Variable -Name System -Scope Local -Force  
77:    Set-Variable -Name Systems -Scope Local -Force  
78:    
79:    $Usernames = @()  
80:    $Systems = @()  
81:    $BitLockerObjects = Get-ADObject -Filter { objectclass -eq 'msFVE-RecoveryInformation' }  
82:    foreach ($System in $BitLockerObjects) {  
83:      $System = $System.DistinguishedName  
84:      $System = $System.Split(',')  
85:      $System = $System[1]  
86:      $System = $System.Split('=')  
87:      $Systems = $Systems + $System[1]  
88:    }  
89:    Return $Systems  
90:    
91:    #Cleanup Local Variables  
92:    Remove-Variable -Name BitLockerObjects -Scope Local -Force  
93:    Remove-Variable -Name System -Scope Local -Force  
94:    Remove-Variable -Name Systems -Scope Local -Force  
95:  }  
96:    
97:  Function Confirm-Bitlockered {  
98:       param ([String[]]$Laptops, [String[]]$BitlockeredSystems)  
99:    
100:    #Declare Local Variables  
101:    Set-Variable -Name Bitlockered -Scope Local -Force  
102:    Set-Variable -Name HeaderRow -Scope Local -Force  
103:    Set-Variable -Name Laptop -Scope Local -Force  
104:    Set-Variable -Name System -Scope Local -Force  
105:         
106:       foreach ($Laptop in $Laptops) {  
107:      $Bitlockered = $false  
108:      foreach ($System in $BitlockeredSystems) {  
109:        If ($Laptop -eq $System) {  
110:          $Bitlockered = $true  
111:        }  
112:      }  
113:      If ($Bitlockered -eq $false) {  
114:        If ((Test-Path $OutputFile) -eq $false) {  
115:          $HeaderRow = "Computers"+[char]44+"Encrypted"+[char]44+"Recovery Key"  
116:          Out-File -FilePath $OutputFile -InputObject $HeaderRow -Force -Encoding UTF8  
117:        }  
118:        Out-File -FilePath $OutputFile -InputObject $Laptop -Append -Force -Encoding UTF8  
119:        Write-Host $Laptop  
120:      }  
121:       }  
122:    
123:    #Cleanup Local Variables  
124:    Remove-Variable -Name Bitlockered -Scope Local -Force  
125:    Remove-Variable -Name HeaderRow -Scope Local -Force  
126:    Remove-Variable -Name Laptop -Scope Local -Force  
127:    Remove-Variable -Name System -Scope Local -Force  
128:  }  
129:    
130:  #Declare Local Variables  
131:  Set-Variable -Name BitlockeredSystems -Scope Local -Force  
132:  Set-Variable -Name Laptops -Scope Local -Force  
133:    
134:  cls  
135:  Import-Module ActiveDirectory -Scope Global -Force  
136:  Import-Module "D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -Force -Scope Global  
137:  $OutputFile = $Path + "\" + $OutputFile  
138:  ProcessTextFile  
139:  $Laptops = Get-Laptops  
140:  $BitlockeredSystems = Get-BitlockeredSystems  
141:  Confirm-Bitlockered -Laptops $Laptops -BitlockeredSystems $BitlockeredSystems  
142:    
143:  #Cleanup Local Variables  
144:  Remove-Variable -Name BitlockeredSystems -Scope Local -Force  
145:  Remove-Variable -Name Laptops -Scope Local -Force  
146:    

24 August 2015

Tracking Unlicensed Software using PowerShell, SCCM, and Orchestrator

Keeping track of systems that have unlicensed software installed can be a daunting task, especially when true up comes. We all have encountered systems that have it installed because the user called in screaming and the help desk went ahead with the install to just shut the user up. I decided that using PowerShell, SCCM, and Orchestrator would make this a much easier process.

I wrote the PowerShell script that will do the following. First comes the query for all systems with the specified software installed. The easiest way is to create the query in SCCM first that returns computer names only. Copy the WQL code for the query and paste it as the data for the $WQL variable.

Next, if you don't already have a collection with all systems that are supposed to have the software installed, then create one. I would suggest allowing only a few admins privs to add to the collection. Get the collection ID and enter that for the Get-CollectionSystems -CollectionID parameter.

The script will delete the previous file created. There are two parameters that need to be populated. Those are -Path and $OutputFile. Path is the location where you want the report file saved and OutputFile is the name of the report file. 

I setup Orchestrator to run the powershell script once a week on the SCCM server. It then sends the generated csv file in an email to all pertinent management for review. Systems popping up on the report can be dealt with immediately thereby keeping the licensing up to date on a weekly basis. 

NOTE: The PowerShell script will need to be executed on the SCCM server.

You can download the script from here.


 <#       
      .NOTES  
      ===========================================================================  
       Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.92  
       Created on:       8/21/2015 1:33 PM  
       Created by:       Mick Pletcher  
       Filename:         LicensedSoftwareVerification.ps1  
      ===========================================================================  
      .DESCRIPTION  
           This script will query SCCM for all systems with a specified   
           software installed. It will then grab all system within a specified  
           collection to compare the query with. The collection is the   
           definitive place where all necessary systems are placed that   
           require the licensed software. Any systems the query sees that are  
           not in the collection will be added to the excel report for further  
           investigation. If the system is valid, it should then be added to  
           the collection.  
 #>  
   
 param  
 (  
      [string]  
      $OutputFile = 'AdobeAcrobatReport.csv',  
      [string]  
      $Path  
 )  
   
 function ProcessTextFile {  
      If ((Test-Path -Path $OutputFile) -eq $true) {  
           Remove-Item -Path $OutputFile -Force  
      }  
 }  
   
 function Get-CollectionSystems {  
   Param([string]$CollectionID)  
   
      #Declare Local Variables  
      Set-Variable -Name System -Scope Local -Force  
      Set-Variable -Name SystemArray -Scope Local -Force  
      Set-Variable -Name Systems -Scope Local -Force  
        
   $SystemArray = @()  
      $Systems = get-cmdevice -collectionid $CollectionID | select name | Sort-Object Name  
   Foreach ($System in $Systems) {  
     $SystemArray = $SystemArray + $System.Name  
   }  
      Return $SystemArray  
        
      #Cleanup Local Variables  
      Remove-Variable -Name System -Scope Local -Force  
      Remove-Variable -Name SystemArray -Scope Local -Force  
      Remove-Variable -Name Systems -Scope Local -Force  
 }  
   
   
 cls  
 Import-Module "D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -Force -Scope Global  
 Set-Location SCCMSiteCode:  
 $CollectionSystems = @()  
 $QuerySystems = @()  
 $UnlicensedSystems = @()  
 #Input the SCCM query code for the $WQL variable  
 $WQL = 'select * from SMS_R_System inner join SMS_G_System_ADD_REMOVE_PROGRAMS on SMS_G_System_ADD_REMOVE_PROGRAMS.ResourceID = SMS_R_System.ResourceId   
   
 where SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName = "Adobe Acrobat 8 Professional" or SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName = "Adobe Acrobat X Pro -   
   
 English, Français, Deutsch" or SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName = "Adobe Acrobat X Standard - English, Français, Deutsch" or   
   
 SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName = "Adobe Acrobat XI Pro" or SMS_G_System_ADD_REMOVE_PROGRAMS.DisplayName = "Adobe Acrobat XI Standard"'  
 $WMI = Get-WmiObject -Namespace Root\SMS\Site_BNA -Query $WQL  
 #Use the collectionID of the collection you use as the definitive licensing site  
 $CollectionSystems = Get-CollectionSystems -CollectionID "SCCM00024"  
 Set-Location c:  
 $OutputFile = $Path + "\" + $OutputFile  
 ProcessTextFile  
 $Output = "Computer Name"  
 Out-File -FilePath $OutputFile -InputObject $Output -Force -Encoding UTF8  
 Foreach ($Item in $WMI) {  
      $QuerySystems = $QuerySystems + $Item.SMS_R_System.Name  
 }  
 Foreach ($QuerySystem in $QuerySystems) {  
   $SystemVerified = $false  
   Foreach ($CollectionSystem in $CollectionSystems) {  
     If ($QuerySystem -eq $CollectionSystem) {  
       $SystemVerified = $true  
     }  
   }  
   If ($SystemVerified -eq $false) {  
     Out-File -FilePath $OutputFile -InputObject $QuerySystem -Force -Encoding UTF8  
   }  
 }  

21 August 2015

PowerShell Approved Verb Cheat Sheet

Here is a cheat sheet that is a list of all PowerShell approved verbs.

You can download cheat sheet from here

14 August 2015

SCCM Inactive Systems Report

I have wanted a PowerShell script that would generate an excel report listing all of the inactive systems, if they are in active directory, and the last time someone logged onto the system if it is in AD. As we all know, it is easy to overlook these systems and they can accumulate, even if the cleanup is setup in SCCM. I chose to use SCCM as the definitive report because it pulls it's initial listing of systems from AD. This script has to be executed on the SCCM server.

I have the script require two parameters: 1) OutputFile and 2) Path. The output file is the name of the CSV file and the Path is the location for the script to write the CSV file to. I have prepopulated the OutputFile parameter, but you can change that if necessary.

The next thing will be that you will need to find your collection ID of your Client Activity: Inactive collection and update that on line 40. If you do not have this collection, you will need to create a collection listing all inactive systems and use the collection ID assigned to it.

Now find out where ConfigurationManager.psd1 is located on your SCCM server and update line 94 with the full path to the module.

The SCCM server needs the PowerShell active directory module feature enabled for this script to function. It is located under RSAT in the Windows Features.

Finally, you will need to execute this script on the SCCM server. I have not found any way around this. I have this script setup in Microsoft Orchestrator that uses PSEXEC.EXE to execute the PowerShell script locally on the SCCM server on a weekly basis. It works flawlessly. Again, this script is running in my company's environment, which will largely differ from others. You will need to modify this script to get it to work in your environment.

You can download the script from here.


1:  <#  
2:            ===========================================================================  
3:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.91  
4:            Created on:       8/13/2015 1:36 PM  
5:            Created by:       Mick Pletcher  
6:            Organization:  
7:            Filename:         InactiveSCCMSystemsReport.ps1  
8:            Description:      This script will retrieve the SCCM inactive systems  
9:                              collection and search active directory to see if it   
10:                             exists there. If so, it will retrieve the last   
11:                             logon date and generate a UTF-8 formatted csv file.  
12:    
13:                             The powershell active directory module will need to be  
14:                             enabled on the SCCM server in order for this script to   
15:                             work correctly. This script will also need to be executed  
16:                             on the SCCM server. You will also need to find the location  
17:                             of ConfigurationManager.psd1 module to import.   
18:            ===========================================================================  
19:  #>  
20:  param  
21:  (  
22:       [string]  
23:       $OutputFile = 'InactiveSCCMSystemsReport.csv',  
24:       [string]  
25:       $Path  
26:  )  
27:  Import-Module ActiveDirectory  
28:    
29:    
30:  function ProcessTextFile {  
31:       If ((Test-Path -Path $OutputFile) -eq $true) {  
32:            Remove-Item -Path $OutputFile -Force  
33:       }  
34:  }  
35:    
36:  function Get-SCCMInactiveSystems {  
37:       #Declare Local Variables  
38:       Set-Variable -Name Systems -Scope Local -Force  
39:         
40:       $Systems = get-cmdevice -collectionid "BNA00093" | select name | Sort-Object Name  
41:       Return $Systems  
42:         
43:       #Cleanup Local Variables  
44:       Remove-Variable -Name Systems -Scope Local -Force  
45:  }  
46:    
47:  function Find-SCCMInactiveSystemInAD {  
48:       param ([string]  
49:            $System)  
50:         
51:       #Declare Local Variables  
52:       Set-Variable -Name AD -Scope Local -Force  
53:       $ErrorActionPreference = 'SilentlyContinue'  
54:       $AD = Get-ADComputer $System  
55:       $ErrorActionPreference = 'Continue'  
56:       if ($AD -ne $null) {  
57:            Return "X"  
58:       } else {  
59:            Return " "       
60:       }  
61:         
62:       #Cleanup Local Variables  
63:       Remove-Variable -Name AD -Scope Local -Force  
64:  }  
65:    
66:  function Get-LastLogonDate {  
67:       param ([string]  
68:            $System)  
69:         
70:       #Declare Local Variables  
71:       Set-Variable -Name AD -Scope Local -Force  
72:         
73:       $AD = Get-ADComputer $System -ErrorAction SilentlyContinue  
74:       $AD = $AD.SamAccountName  
75:       $AD = $AD.Substring(0, $AD.Length - 1)  
76:       $AD = Get-ADComputer -Identity $AD -Properties *  
77:       $AD = $AD.LastLogonDate  
78:       Return $AD  
79:              
80:       #Cleanup Local Variables  
81:       Remove-Variable -Name AD -Scope Local -Force  
82:  }  
83:    
84:  #Declare Variables  
85:  Set-Variable -Name ADEntry -Scope Local -Force  
86:  Set-Variable -Name Counter -Value 1 -Scope Local -Force  
87:  Set-Variable -Name LastLogon -Scope Local -Force  
88:  Set-Variable -Name Output -Scope Local -Force  
89:  Set-Variable -Name SCCMInactiveSystems -Scope Local -Force  
90:  Set-Variable -Name System -Scope Local -Force  
91:    
92:  cls  
93:  Import-Module -Name ActiveDirectory  
94:  Import-Module "D:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1" -Force -Scope Global  
95:  Set-Location BNA:  
96:  $SCCMInactiveSystems = Get-SCCMInactiveSystems  
97:  Set-Location c:  
98:  $OutputFile = $Path + "\" + $OutputFile  
99:  ProcessTextFile  
100:  $Output = "Computer Name" + [char]44+"Active Directory"+[char]44+"Last Logon"  
101:  Out-File -FilePath $OutputFile -InputObject $Output -Force -Encoding UTF8  
102:  foreach ($System in $SCCMInactiveSystems) {  
103:       cls  
104:       $Output = "Processing "+$System.Name+" -- "+$Counter+" of "+$SCCMInactiveSystems.Count  
105:       Write-Host $Output  
106:       $Counter++  
107:       $ADEntry = Find-SCCMInactiveSystemInAD -System $System.Name  
108:       If ($ADEntry -ne " ") {  
109:            $LastLogon = Get-LastLogonDate -System $System.Name  
110:       }  
111:       $Output = $System.Name+[char]44+$ADEntry+[char]44+$LastLogon  
112:       Out-File -FilePath $Global:OutputFile -InputObject $Output -Append -Force -Encoding UTF8  
113:       $ADEntry = $null  
114:       $LastLogon = $null  
115:       $Output = $null  
116:  }  
117:    
118:  #Cleanup Variables  
119:  Remove-Variable -Name ADEntry -Scope Local -Force  
120:  Remove-Variable -Name Counter -Scope Local -Force  
121:  Remove-Variable -Name LastLogon -Scope Local -Force  
122:  Remove-Variable -Name Output -Scope Local -Force  
123:  Remove-Variable -Name SCCMInactiveSystems -Scope Local -Force  
124:  Remove-Variable -Name System -Scope Local -Force  
125:    

10 August 2015

Windows Updates Reporting Tool

My firm uses MDT to build all of our computers. The windows updates are setup as packages so they can be applied to the OS before it is laid down on the PC. MDT will then download any new updates and apply them to the PC after the OS has been laid down. In order to keep track of new updates needing to be integrated as a package, I have written the following PowerShell script that will generate a CSV file listing all of the updates installed during the build and in the exact order they were installed. This gives a clean report of the updates instead of having to dig through the ZTIWindowsUpdate.log, which is what this report is generated from. I have integrated this script into the task sequence to execute immediately after the task Windows Update (Post-Application Installation). There are two parameters that need to be defined when calling the script. These are:

  1. OutputFile - give the name of the file you desire such as BaseBuildUpdatesReport.csv
  2. Path - Defines the location where you want the OutputFile to be written to
Here is an example of a command line:
  • powershell.exe -executionpolicy bypass -file WindowsUpdatesReport.ps1 -OutputFile BaseBuild.csv -Path \\NetworkLocation\Directory
Here is a screenshot setting it up as a task sequence in MDT:

You can download the code from here.




 <#       
      .NOTES  
      ===========================================================================  
       Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.90  
       Created on:       8/7/2015 9:54 AM  
       Created by:       Mick Pletcher  
       Filename:        WindowsUpdatesReport.ps1  
      ===========================================================================  
      .DESCRIPTION  
           This script will extract the list of windows updates installed   
           during an MDT installation.  
      .EXAMPLE  
           powershell.exe -executionpolicy bypass -file WindowsUpdatesReport.ps1 -OutputFile BaseBuild.csv -Path \\NetworkLocation\Directory  
 #>  
   
 param ([string]$OutputFile, [string]$Path)  
   
 function Get-RelativePath {  
      #Declare Local Variables  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $RelativePath  
        
      #Cleanup Local Variables  
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 function ProcessTextFile {  
      #Declare Local Variables   
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      If ((Test-Path -Path $RelativePath$OutputFile) -eq $true) {  
           Remove-Item -Path $RelativePath$OutputFile -Force  
      }  
        
      #Cleanup Local Variables   
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 function Get-Updates {  
      #Declare Local Variables  
      Set-Variable -Name File -Scope Local -Force  
      Set-Variable -Name Line -Scope Local -Force  
      Set-Variable -Name LogFile -Scope Local -Value $env:SystemDrive"\MININT\SMSOSD\OSDLOGS\ZTIWindowsUpdate.log" -Force  
      Set-Variable -Name Name -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $OutputArray = @()  
      $RelativePath = Get-RelativePath  
      $File = Get-Content -Path $LogFile  
      $Global:OutputFile = $RelativePath + $Global:OutputFile  
      $Output = "KB Article" + "," + "Description"  
      Out-File -FilePath $Global:OutputFile -InputObject $Output -Append -Force -Encoding UTF8  
      If ($File -ne $null) {  
           foreach ($Line in $File) {  
                Set-Variable -Name KB -Scope Local -Force  
                If ($Line -like "*INSTALL - *") {  
                     $Name = $Line  
                     $Name = $Name -replace 'x64-based', 'x64 based'  
                     $Name = $Name -replace '32-Bit', '32 Bit'  
                     $Name = $Name.split('-')  
                     If ($Name[7] -like "*Definition*") {  
                          $KB = $Name[7]  
                          $KB = $KB.Trim()  
                          $KB = $KB.split(' ')  
                          $KB = $KB.Trim()  
                          [string]$KB = $KB[0]  
                          $Name = $Name[6]  
                          $Name = $Name.Trim()  
                     } else {  
                          $KB = $Name[6]  
                          $KB = $KB.split('(')  
                          $KB = $KB.split(')')  
                          $KB = $KB.Trim()  
                          $KB = $KB[1]  
                          $Name = $Name[6]  
                          $Name = $Name.split('(')  
                          $Name = $Name[0]  
                          $Name = $Name.Trim()  
                     }  
                     $Output = $KB + "," + $Name  
                     $OutputArray = $OutputArray + $Output  
                     Remove-Variable -Name KB -Scope Local -Force  
                }  
           }  
           $Line = $null  
           $OutputArray = $OutputArray | select -Unique  
           foreach ($Line in $OutputArray) {  
                Out-File -FilePath $Global:OutputFile -InputObject $Line -Append -Force -Encoding UTF8  
           }  
      } else {  
           $Output = "No User Input Properties Exist"  
           Write-Host $Output  
           Out-File -FilePath $Global:OutputFile -InputObject $Output -Append -Force -Encoding UTF8  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name File -Scope Local -Force  
      Remove-Variable -Name KB -Scope Local -Force  
      Remove-Variable -Name Line -Scope Local -Force  
      Remove-Variable -Name Name -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 cls  
 ProcessTextFile  
 Get-Updates  
   

07 August 2015

SCCM Client Installer

This PowerShell script will uninstall previous versions of SCCM client, run the CCMClean to make sure all instances of the client are gone off of the machine, and finally install the SCCM client. The script will not complete until the installation of the SCCM client is completed. I used this when upgrading from 2007 to 2012 and also have this integrated into the build. I pulled the functions from my installation module and put them into this script so that it can be a standalone. One thing you will need to do, if you want to include the ccmclean.exe, is to download it from here.

You can download the script below from here.


1:  <#       
2:       .NOTES  
3:       ===========================================================================  
4:        Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2015 v4.2.90  
5:        Created on:       7 August 2015 12:18 PM  
6:        Created by:       Mick Pletcher  
7:        Organization:        
8:        Filename:        installSCCMClient_Standalone.ps1  
9:       ===========================================================================  
10:       .DESCRIPTION  
11:            This script will uninstall all previous versions of SCCM Client,  
12:            including the execution of the ccmclean.exe to rid the system of  
13:            any remnants of the previous client. It will then install the new  
14:            client and wait until the client is installed and is communicating  
15:            with the SCCM server.  
16:    
17:  #>  
18:    
19:  #Declare Global Variables  
20:  Set-Variable -Name RelativePath -Scope Global -Force  
21:  Set-Variable -Name Title -Scope Global -Force  
22:    
23:  Function InitializeVariables {  
24:       $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
25:       $Global:Title = "SCCM 2012 R2 Client"  
26:  }  
27:    
28:  Function Install-EXE {  
29:       <#  
30:       .SYNOPSIS  
31:            Install-EXE  
32:       .DESCRIPTION  
33:            Installs an EXE file  
34:       .EXAMPLE  
35:            Install-EXE -DisplayName "Microsoft Office 2013" -Executable "c:\temp\install.exe" -Switches "/passive /norestart"  
36:       #>  
37:         
38:       Param ([String]$DisplayName,  
39:            [String]$Executable,  
40:            [String]$Switches)  
41:         
42:       Write-Host "Install"$DisplayName"....." -NoNewline  
43:       If ((Test-Path $Executable) -eq $true) {  
44:            $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode  
45:       } else {  
46:            $ErrCode = 1  
47:       }  
48:       If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
49:            Write-Host "Success" -ForegroundColor Yellow  
50:       } else {  
51:            Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
52:       }  
53:  }  
54:    
55:  Function Install-MSP {  
56:       <#  
57:       .SYNOPSIS  
58:            Install-MSP  
59:       .DESCRIPTION  
60:            Installs an MSP patch  
61:       .EXAMPLE  
62:            Install-MSP -DisplayName "KB977203" -MSP $Global:RelativePath"i386\sccm2007ac-sp2-kb977203-x86.msp" -Switches "/qb- /norestart"  
63:       #>  
64:         
65:       Param ([String]$DisplayName,  
66:            [String]$MSP,  
67:            [String]$Switches)  
68:         
69:       $Executable = $Env:windir + "\system32\msiexec.exe"  
70:       $Parameters = "/p " + [char]34 + $MSP + [char]34 + [char]32 + $Switches  
71:       Write-Host "Install"$DisplayName"....." -NoNewline  
72:       $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
73:       If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
74:            Write-Host "Success" -ForegroundColor Yellow  
75:       } else {  
76:            Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
77:       }  
78:  }  
79:    
80:  Function Set-ConsoleTitle {  
81:       <#  
82:       .SYNOPSIS  
83:            Set-ConsoleTitle  
84:       .DESCRIPTION  
85:            Renames the PowerShell console window  
86:       .EXAMPLE  
87:            Set-ConsoleTitle -Title "Test"  
88:       #>  
89:         
90:       Param ([String]$Title)  
91:       $host.ui.RawUI.WindowTitle = $Title  
92:  }  
93:    
94:  Function Stop-Task {  
95:       <#  
96:       .SYNOPSIS  
97:            Stop-Task  
98:       .DESCRIPTION  
99:            Kills a designated Task  
100:       .EXAMPLE  
101:            Stop-Task -Process "outlook"  
102:       #>  
103:         
104:       Param ([String]$Process)  
105:         
106:       $Proc = Get-Process $Process -ErrorAction SilentlyContinue  
107:       Write-Host "Killing"$Process"....." -NoNewline  
108:       If ($Proc -ne $null) {  
109:            Do {  
110:                 $ProcName = $Process + ".exe"  
111:                 $Temp = taskkill /F /IM $ProcName  
112:                 Start-Sleep -Seconds 2  
113:                 $Proc = $null  
114:                 $Proc = Get-Process $Process -ErrorAction SilentlyContinue  
115:            } While ($Proc -ne $null)  
116:            Write-Host "Closed" -ForegroundColor Yellow  
117:       } else {  
118:            Write-Host "Already Closed" -ForegroundColor Green  
119:       }  
120:  }  
121:    
122:  Function Uninstall-EXE {  
123:       <#  
124:       .SYNOPSIS  
125:            Uninstall-EXE  
126:       .DESCRIPTION  
127:            Uninstalls an EXE file  
128:       .EXAMPLE  
129:            Uninstall-EXE -DisplayName "Microsoft Office 2013" -Executable "c:\temp\setup.exe" -Switches "/uninstall /passive /norestart"  
130:       #>  
131:         
132:       Param ([String]$DisplayName,  
133:            [String]$Executable,  
134:            [String]$Switches)  
135:         
136:       Write-Host "Uninstall"$DisplayName"....." -NoNewline  
137:       $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Switches -Wait -Passthru).ExitCode  
138:       If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
139:            Write-Host "Success" -ForegroundColor Yellow  
140:       } else {  
141:            Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
142:       }  
143:  }  
144:    
145:  Function Uninstall-MSIByGUID {  
146:       <#  
147:       .SYNOPSIS  
148:            Uninstall-MSIByGUID  
149:       .DESCRIPTION  
150:            Uninstalls an MSI application using the GUID  
151:       .EXAMPLE  
152:            Uninstall-MSIByGUID -DisplayName "Workshare Professional" -GUID "{8686EC18-6282-4AA9-92AC-2865B972E244}" -Switches "/qb- /norestart"  
153:       #>  
154:         
155:       Param ([String]$DisplayName,  
156:            [String]$GUID,  
157:            [String]$Switches)  
158:         
159:       $Executable = $Env:windir + "\system32\msiexec.exe"  
160:       $Parameters = "/x " + $GUID + [char]32 + $Switches  
161:       Write-Host "Uninstall"$DisplayName"....." -NoNewline  
162:       $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
163:       If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
164:            Write-Host "Success" -ForegroundColor Yellow  
165:       } elseIf ($ErrCode -eq 1605) {  
166:            Write-Host "Not Present" -ForegroundColor Green  
167:       } else {  
168:            Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
169:       }  
170:  }  
171:    
172:  Function Uninstall-MSIByName {  
173:       <#  
174:       .SYNOPSIS  
175:            Uninstall-MSIByName  
176:       .DESCRIPTION  
177:            Uninstalls an MSI application using the MSI file  
178:       .EXAMPLE  
179:            Uninstall-MSIByName -ApplicationName "Adobe Reader" -Switches "/qb- /norestart"  
180:       #>  
181:         
182:       Param ([String]$ApplicationName,  
183:            [String]$Switches)  
184:         
185:       $Uninstall = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
186:       $Uninstall += Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
187:       $SearchName = "*" + $ApplicationName + "*"  
188:       $Executable = $Env:windir + "\system32\msiexec.exe"  
189:       Foreach ($Key in $Uninstall) {  
190:            $TempKey = $Key.Name -split "\\"  
191:            If ($TempKey[002] -eq "Microsoft") {  
192:                 $Key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $Key.PSChildName  
193:            } else {  
194:                 $Key = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" + $Key.PSChildName  
195:            }  
196:            If ((Test-Path $Key) -eq $true) {  
197:                 $KeyName = Get-ItemProperty -Path $Key  
198:                 If ($KeyName.DisplayName -like $SearchName) {  
199:                      $TempKey = $KeyName.UninstallString -split " "  
200:                      If ($TempKey[0] -eq "MsiExec.exe") {  
201:                           Write-Host "Uninstall"$KeyName.DisplayName"....." -NoNewline  
202:                           $Parameters = "/x " + $KeyName.PSChildName + [char]32 + $Switches  
203:                           $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
204:                           If (($ErrCode -eq 0) -or ($ErrCode -eq 3010) -or ($ErrCode -eq 1605)) {  
205:                                Write-Host "Success" -ForegroundColor Yellow  
206:                           } else {  
207:                                Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
208:                           }  
209:                      }  
210:                 }  
211:            }  
212:       }  
213:  }  
214:    
215:  Function Wait-ProcessEnd {  
216:       <#  
217:       .SYNOPSIS  
218:            Wait-Process  
219:       .DESCRIPTION  
220:            Waits for a Process to end before continuing.  
221:       .EXAMPLE  
222:            Wait-Process -Process "explorer"  
223:       #>  
224:         
225:       Param ([String]$Process)  
226:         
227:       Write-Host "Waiting for"$Process" to end....." -NoNewline  
228:       $Proc = Get-Process $Process -ErrorAction SilentlyContinue  
229:       If ($Proc -ne $null) {  
230:            Do {  
231:                 Start-Sleep -Seconds 5  
232:                 $Proc = Get-Process $Process -ErrorAction SilentlyContinue  
233:            } While ($Proc -ne $null)  
234:            Write-Host "Ended" -ForegroundColor Yellow  
235:       } else {  
236:            Write-Host "Process Already Ended" -ForegroundColor Yellow  
237:       }  
238:  }  
239:    
240:  cls  
241:  InitializeVariables  
242:  Set-ConsoleTitle -Title $Global:Title  
243:  Stop-Task -Process "msiexec"  
244:  Uninstall-MSIByName -ApplicationName "Configuration Manager Client" -Switches "/qb- /norestart"  
245:  Stop-Task -Process "msiexec"  
246:  Uninstall-MSIByGUID -DisplayName "SCCM 2007 Client" -GUID "{2609EDF1-34C4-4B03-B634-55F3B3BC4931}" -Switches "/qb- /norestart"  
247:  Stop-Task -Process "msiexec"  
248:  Uninstall-MSIByGUID -DisplayName "SCCM 2012 Client" -GUID "{BFDADC41-FDCD-4B9C-B446-8A818D01BEA3}" -Switches "/qb- /norestart"  
249:  Stop-Task -Process "msiexec"  
250:  Uninstall-EXE -DisplayName "CCMClean" -Executable $Global:RelativePath"ccmclean.exe" -Switches "/all /logdir:%windir%\waller\logs /removehistory /q"  
251:  Stop-Task -Process "msiexec"  
252:  Install-EXE -DisplayName "SCCM 2012 R2 Client" -Executable $Global:RelativePath"ccmsetup.exe" -Switches "/mp:bnasccm.wallerlaw.int SMSSITECODE=BNA"  
253:  Wait-ProcessEnd -Process "CCMSETUP"  
254:  Stop-Task -Process "msiexec"  
255:    
256:  #Cleanup Global Variables  
257:  Remove-Variable -Name RelativePath -Scope Global -Force  
258:  Remove-Variable -Name Title -Scope Global -Force