27 February 2015

Cleaning up old systems in Active Directory, SCCM, and Antivirus

Every place I have worked, there has been the issue of systems being in SCCM, AD, and antivirus that no longer existed. The is often caused by systems being overlooked when a user departs the company, a laptop that gets put in a desk and not turned on forever, and a lot of other similar scenarios.

While I was in the process of upgrading SCCM 2007 to 2012, I finally became fed up over it and wrote this script. The script uses All Systems from SCCM as the master list. Here is why. The way I have the SCCM configured is that All Systems is populated by AD. Logically, that means that even if a system has been deleted in AD, it might still be present in SCCM. We use a third party antivirus, so I integrated it by importing a list of all systems it showed with the antivirus client.

The script will first read a system from the SCCM list named SCCMSystems.txt. It reads the antivirus systems from a text file called AntivirusSystems.txt. The text files should be in UTF-8 format. It then runs through three tests. The first test is to query AD. The second test is to check if the system name is included in the antivirus list. The third test is to run a network connectivity test. If the network connectivity test fails, it runs a secondary ping test and reads the output of ping.exe to see if it returns host not found. The Network Connectivity test fails only if a host not found is returned. It passes for any other message because it could be a laptop that is offline at the time.

There is a csv file that is written to for all failures. The csv file contains two primary columns, deletions and additions. Under the deletions, it has AD, antivirus, and SCCM. Under the additions if antivirus. An X is placed in each box indicating the recommended action.

If you do not want/need the antivirus, you can delete that portion out of the script. This script is only a primer for instituting in your organization. It will need to be adapted to your network.

You can download the script from here.




 <#  
 .SYNOPSIS  
   ValidateSystems  
 .DESCRIPTION  
   Validate if a system still exists  
 .Author  
   Mick Pletcher  
 .Date  
   26 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file ValidateSystems.ps1  
 #>  
   
 Function InitializeGlobalMemory {  
      Set-Variable -Name Computers -Scope Global -Force  
      Set-Variable -Name Logfile -Scope Global -Force  
      Set-Variable -Name RelativePath -Scope Global -Force  
      Set-Variable -Name Webroot -Scope Global -Force  
      $Global:Failures = @()  
      $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"   
      $Global:LogFile = $Global:RelativePath + "Output.csv"  
      $Global:Computers = Get-Content -Path $Global:RelativePath"SCCMSystems.txt" -Force  
      $Global:Webroot = Get-Content -Path $Global:RelativePath"AntivirusSystems.txt" -Force  
   
 }  
   
 Function ProcessLogFile {  
      If ((Test-Path $Global:LogFile) -eq $true) {  
           Remove-Item $Global:LogFile -Force  
      }  
      If ((Test-Path $Global:LogFile) -eq $false) {  
           $temp = New-Item $Global:LogFile -ItemType File -Force  
           $Output = ","+"Deletions"+","+","+","+"Additions"  
           Out-File -FilePath $Global:LogFile -InputObject $Output -Append -Force -Encoding UTF8  
           $Output = "Computer Name"+","+"Active Directory"+","+"SCCM"+","+"Antivirus"+","+"Antivirus"  
           Out-File -FilePath $Global:LogFile -InputObject $Output -Append -Force -Encoding UTF8  
      }  
 }  
   
 Function ProcessComputers {  
      $obj = New-Object PSObject  
      $Count = 0  
      Foreach ($Computer in $Global:Computers) {  
           cls  
           $Antivirus = $false  
           $Count += 1  
           Write-Host "Processing "$Count" of " -NoNewline  
           Write-Host $Global:Computers.Count  
           Write-Host  
           Write-Host "Computer Name: "$Computer  
           $ADAccount = $null  
           #Active Directory  
           Write-Host "Testing AD Presence....." -NoNewline  
           $ErrorActionPreference = 'SilentlyContinue'  
           $ADAccount = Get-ADComputer $Computer  
           If ($ADAccount -eq $null) {  
                $ADAccount = $false  
                Write-Host "Does not Exist" -ForegroundColor Red  
           } else {  
                $ADAccount = $true  
                Write-Host "Exists" -ForegroundColor Yellow  
           }  
           #Antivirus  
           Write-Host "Testing Antivirus....." -NoNewline  
           Foreach ($system in $Global:Webroot) {  
                If ($system -eq $Computer) {  
                     $Antivirus = $true  
                }  
           }  
           If ($Antivirus -eq $true) {  
                Write-Host "Exists" -ForegroundColor Yellow  
           } else {  
                Write-Host "Does not exist" -ForegroundColor Red  
           }  
           #Network Connectivity  
           Write-Host "Testing Network Connectivity....." -NoNewline  
           If ((Test-Connection -ComputerName $Computer -Quiet) -eq $false) {  
                $NetworkTest = ping $Computer  
                If ($NetworkTest -like '*Ping request could not find host*') {  
                     $NetworkTest = $false  
                     Write-Host "Does not exist" -ForegroundColor Red  
                } else {  
                     $NetworkTest = $true  
                     Write-Host "Exists" -ForegroundColor Yellow  
                }  
           } else {  
                $NetworkTest = $true  
                Write-Host "Exists" -ForegroundColor Yellow  
           }  
           If (($ADAccount -eq $true) -and ($NetworkTest -eq $false)) {  
                #Write-Host $Computer -NoNewline  
                #Write-Host " - Delete from AD and SCCM" -BackgroundColor Yellow -ForegroundColor Black  
                $obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer  
                If ($Antivirus -eq $true) {  
                     $obj | Add-Member -MemberType NoteProperty -Name Action -Value "AD_SCCM_Antivirus"  
                } else {  
                     $obj | Add-Member -MemberType NoteProperty -Name Action -Value "AD_SCCM"  
                }  
           }  
           If (($ADAccount -eq $false) -and ($NetworkTest -eq $false)) {  
                #Write-Host $Computer -NoNewline  
                #Write-Host " - Delete from SCCM" -BackgroundColor Green -ForegroundColor Black  
                $obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer  
                If ($Antivirus -eq $true) {  
                     $obj | Add-Member -MemberType NoteProperty -Name Action -Value "SCCM_Antivirus"  
                } else {  
                     $obj | Add-Member -MemberType NoteProperty -Name Action -Value "SCCM"  
                }  
           }  
           If (($ADAccount -eq $true) -and ($NetworkTest -eq $true) -and ($Antivirus -eq $false)) {  
                $obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $Computer  
                $obj | Add-Member -MemberType NoteProperty -Name Action -Value "AddAntivirus"  
           }  
           If ($obj -ne $null) {  
                $Global:Failures += $obj  
                $Output = $obj.ComputerName  
                If ($obj.Action -eq "AD_SCCM") {  
                     $Output = $Output + ","+"X"+","+"X"   
                }  
                If ($obj.Action -eq "AD_SCCM_Antivirus") {  
                     $Output = $Output + ","+"X"+","+"X"+","+"X"  
                }  
                If ($obj.Action -eq "SCCM") {  
                     $Output = $Output + ","+","+"X"  
                }  
                If ($obj.Action -eq "SCCM_Antivirus") {  
                     $Output = $Output + ","+","+"X"+","+"X"  
                }  
                If ($obj.Action -eq "AddAntivirus") {  
                     $Output = $Output + ","+","+","+","+"X"  
                }  
                Out-File -FilePath $Global:LogFile -InputObject $Output -Append -Force -Encoding UTF8  
                Remove-Variable -Name obj  
                $obj = New-Object PSObject  
           }  
           Start-Sleep -Seconds 1  
      }  
 }  
   
 Function WriteToScreen {  
      cls  
      Foreach ($Failure in $Global:Failures) {  
           If ($Failure -ne $null) {  
                Write-Output $Failure  
           }  
      }  
      $ErrorActionPreference = 'Continue'  
 }  
   
 cls  
 Import-Module -Name ActiveDirectory  
 InitializeGlobalMemory  
 ProcessLogFile  
 ProcessComputers  
 WriteToScreen  
   

23 February 2015

Import and Apply Local GPOs

This script will import and apply a local GPO using the local GPO utility, ImportRegPol.exe, located here. The script is a wrapper that makes implementing this utility a snap. All that has to be done is to use the Microsoft Security Compliance Manager to export the desired local GPO. I wrote this script for use mainly in the MDT build. I realize there is the GPO Pack built into MDT, but what happens when you want to deploy a local GPO to machines already built or multiple local GPOs at different times in a build? This script makes that easy.

The syntax for the function is as follows:

Syntax:
Import-LGPO -LGOPName "User Friendly Name" -LGPOLocation "<Path_To_GPO_GUID>" -GPOType "Machine"

Example:
Import-LGPO -LGOPName "Disable Network Wait" -LGPOLocation "\\Fileshare\LGPO\{57D203F7-B8CE-47BC-920F-CECF34F6A6BA}" -GPOType "Machine"

You can download the script from here.



 <#  
 .SYNOPSIS  
   Apply Local Group Policy  
 .Author  
   Mick Pletcher  
 .Date  
   23 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file LGPO.ps1  
 #>  
   
   
 Function Import-LGPO {  
   
      Param([String]$LGPOName, [String]$LGPOLocation, [String]$GPOType)  
        
      $Executable = $Global:RelativePath+"ImportRegPol.exe"  
      If ($GPOType -eq "Machine") {  
           $GPOType = "\DomainSysvol\GPO\Machine\registry.pol"  
      } else {  
           $GPOType = "\DomainSysvol\GPO\User\registry.pol"  
      }  
      $Parameters = "-m "+[char]34+$LGPOLocation+$GPOType+[char]34  
      Write-Host "Apply Local"$LGPOName" Policy....." -NoNewline  
      $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
      If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
      }  
   
 }  
   
 cls  
 $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"  
 Import-LGPO -LGPOName "User Friendly Name" -LGPOLocation "<Path_To_GPO_GUID>" -GPOType "Machine"  
 Start-Sleep -Seconds 5  
   

18 February 2015

SCCM 2012 R2 Client Uninstalls after Reboot

I encountered an issue with SCCM 2012 R2 when deploying the client to systems that were being upgraded from SCCM 2007. What would happen was that I pushed the client to the system through the console using the Install Client option. The client cleanly uninstalled the 2007 client and installed the 2012 client with no issues. Upon the first reboot of the system, the client would be completely uninstalled. The logs showed little information. The next thing I did was to manually install the client from a command line thinking there might be something going on when the server was trying to administer the installation. The same thing happened. The only option I could think of was to delete the computer from sccm and then manually reinstall the client. This worked and the client remained installed on the system.

14 February 2015

List all Local Administrators on a Computer

I wrote this script to generate a list of local administrators on a PC. It saves the output to a text file at a central repository. The text file is named the computer name and contains a listing of all the local administrators. It can be pushed out to run as a package in SCCM. The second script will read all of the text files and create a csv file with all of the combined data.

You can download the script below:



 <#  
 .SYNOPSIS  
   Get a list of local administrators on a machine  
 .DESCRIPTION  
   Get a list of local administrators on a machine and write the list to  
   a csv file on a remote share.  
 .Author  
   Mick Pletcher  
 .Date  
   14 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file LocalAdministrators.ps1  
 #>  
   
 cls  
 $LocalAdmins = @()  
 $Members = net localgroup administrators | where {$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4  
 $Profiles = Get-ChildItem -Path $env:SystemDrive"\users" -Force  
 $LogFile = "\\NetworkLocation\"+$env:COMPUTERNAME+".log"  
   
 Foreach ($Member in $Members) {  
      $Member = $Member.Split("\")  
      If ($Member.Count -gt 1) {  
           [string]$Member = $Member[1]  
           If (($Member -ne "Domain Admins") -and ($Member -ne "Workstation Admins")) {  
                Foreach ($Prof in $Profiles) {  
                     If ($Member -eq $Prof) {  
                          $LocalAdmins += $Member  
                     }  
                }  
           }  
      }  
      Remove-Variable -Name Member  
 }  
 If ((Test-Path $LogFile) -eq $true) {  
      Remove-Item -Path $LogFile -Force  
 }  
 If ((Test-Path $LogFile) -eq $false) {  
      New-Item -Path $LogFile -ItemType File -Force  
 }  
 If ($LocalAdmins.Count -gt 0) {  
      Foreach ($LocalAdmin in $LocalAdmins) {  
           Add-Content -Path $LogFile -Value $LocalAdmin -Force  
      }  
 }  
   


 <#  
 .SYNOPSIS  
   Create Local Administrators Report  
 .DESCRIPTION  
   This will read all .log files and consolidate them into a master   
   .csv file with the computer name and list of local admins for  
   each computer  
 .Author  
   Mick Pletcher  
 .Date  
   14 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file LocalAdministratorsReport.ps1  
 #>  
   
 $MasterLog = "\\NetworkLocation\LocalAdministrators.csv"  
 $Files = Get-ChildItem -Path \\NetworkLocation -Force  
 If ((Test-Path $MasterLog) -eq $true) {  
      Remove-Item -Path $MasterLog -Force  
 }  
 If ((Test-Path $MasterLog) -eq $false) {  
      $TitleBar = "ComputerName,UserName"+[char]13  
      New-Item -Path $MasterLog -ItemType File -Value $TitleBar -Force  
 }  
 Foreach ($File in $Files) {  
      If ($File.Extension -eq ".log") {  
           $Usernames = Get-Content -Path $File.FullName  
           Foreach ($Username in $Usernames) {  
                $Entry = $File.BaseName+","+$Username  
                Add-Content -Path $MasterLog -Value $Entry -Force  
           }  
      }  
 }  
   

12 February 2015

List of Installed Updates

This script will generate a clean list of Microsoft updates that were installed during a system build, making it drastically easier than having to parse through the log file. You open the powershell script up and update the $File variable to point to the location of the BDD.log file.

You can download the script from here.


 <#  
 .SYNOPSIS  
   Produce list of newly installed windows updates  
 .DESCRIPTION  
   Reads BDD.log file from a build and extracts the list of new updates  
   that were applied.  
 .Author  
   Mick Pletcher  
 .Date  
   12 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file UpdateList.ps1  
 #>  
   
 cls  
 $File = Get-Content -Path "C:\Users\Mick\Desktop\BDD.log" -Force  
 Foreach ($Entry in $File) {  
      If (($Entry -like '*INSTALL - *') -and ($Entry -like '*ZTIWindowsUpdate*')) {  
           #Write-Host $Entry  
           $SplitLine = $Entry.Split('KB')  
           $Update = $SplitLine[2]  
           $Update = $Update.Split(')')  
           $Update = $Update.Split('(')  
           Write-Host "KB"$Update[0]  
      }  
 }  
   
 Remove-Variable -Name Entry -Force  
 Remove-Variable -Name File -Force  
 Remove-Variable -Name Update -Force  

09 February 2015

Deploying Windows Management Framework 4.0

This PowerShell script will install WMF 4.0. It will return an error if the installation fails.

You can download the script from here.


 <#  
 .Author  
      Mick Pletcher  
 .Date  
      29 July 2014  
 .SYNOPSIS  
   Windows Management Framework 4.0  
 .EXAMPLE  
      powershell.exe -executionpolicy bypass -file install_WMF4.ps1  
 #>  
   
 #Declare Global Memory  
 Set-Variable -Name Errors -Value $null -Scope Global -Force  
 Set-Variable -Name RelativePath -Scope Global -Force  
 Set-Variable -Name Title -Scope Global -Force  
   
 Function ConsoleTitle ($Title){  
      $host.ui.RawUI.WindowTitle = $Title  
 }  
   
 Function DeclareGlobalVariables {  
      $Global:Title = "Windows Management Framework 4.0"  
 }  
   
 Function GetRelativePath {   
      $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"   
 }  
   
 Function InstallMSU ($ProgName,$MSU,$Switches) {  
      $EXE = $Env:windir+"\system32\wusa.exe"  
      $Parameters = [char]34+$MSU+[char]34+[char]32+$Switches  
      Write-Host "Install "$ProgName"....." -NoNewline  
      $ErrCode = (Start-Process -FilePath $EXE -ArgumentList $Parameters -Wait -Passthru).ExitCode  
      If (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } elseif ($ErrCode -eq 2359302) {  
           Write-Host "Already Installed" -ForegroundColor Green  
      } else {  
           Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
           $Global:Errors++  
      }  
 }  
   
 Function ExitPowerShell {  
      If (($Global:Errors -ne $null) -and ($Global:Errors -ne 0)) {  
           Exit 1  
      }  
 }  
   
 cls  
 DeclareGlobalVariables  
 GetRelativePath  
 ConsoleTitle $Global:Title  
 InstallMSU "Windows Management Framework 4.0" $Global:RelativePath"Windows6.1-KB2819745-x86-MultiPkg.msu" "/quiet /norestart"  
 Start-Sleep -Seconds 5  
 ExitPowerShell  
   

06 February 2015

Deploying CMTrace

CMTrace makes reading .log files much easier. Deploying it though can be somewhat tricky. I have written this PowerShell script that will install CMTrace and associates .log with the application. The association portion of this only works if the user has not manually associated a .log file. I have also written this to be compatible with both 32-bit and 64-bit systems.

All that needs to be done here is to make sure the CMTrace.exe is in the same directory as the PowerShell script. 

You can download the script from here.


NOTE: There is a newer and more robust version of the CMTrace installed located here


 <#  
 .SYNOPSIS  
   Install CMTrace  
 .DESCRIPTION  
   Install CMTrace.exe and associate .log files  
 .Author  
   Mick Pletcher  
 .Date  
   06 February 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file install.ps1  
 #>  
   
 $RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"   
 $Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
 $Architecture = $Global:Architecture.OSArchitecture  
 If ($Architecture -eq "32-bit") {  
      Copy-Item -Path $RelativePath"CMTrace.exe" -Destination $env:ProgramFiles"\Windows NT\Accessories" -Force  
      $Parameters = "Ftype logfile="+[char]34+$env:ProgramFiles+"\Windows NT\Accessories\CMTrace.exe"+[char]34+" %1"  
      cmd.exe /c $Parameters  
 } else {  
      Copy-Item -Path $RelativePath"CMTrace.exe" -Destination ${env:ProgramFiles(x86)}"\Windows NT\Accessories" -Force  
      $Parameters = "FType logfile="+[char]34+${env:ProgramFiles(x86)}+"\Windows NT\Accessories\CMTrace.exe"+[char]34+" %1"  
      cmd.exe /c $Parameters  
 }  
 $Parameters = "assoc .log=logfile"  
 cmd.exe /c $Parameters