Mick's IT Blogs

Mick's IT Blogs

Latest Updates

22 February 2017

Clearing Specific Print Queues

Posted By: Mick Pletcher - 2:48 PM














In a recent deployment, we ran into an issue where there had been print jobs stuck in the print que on some machines. We thought we had it fixed by running the following cmdlet:

Get-WmiObject Win32_Printer | where-object { $_.Name -like "*Workshare*" } | foreach-object { $_.CancelAllJobs() }

It worked on some machines, but others were not clearing. We did not want to delete print jobs to other printers. We had to find an alternative method to clear the jobs out. The script below was written to look at jobs in the %System32%\spool\printers directory. It will get a list of .SHD files. It will then open up the file and read the contents, filtering out everything except for alphabetical letters and then converting them to ASCII. Next, it searches the converted text for a partial or full name of the printer specified in the parameters, and marks it to be deleted. The script will then stop the print spooler, delete the files, and then restart the spooler. This script resolved our issue for making sure the specific printer was cleared of any print jobs. 

You can download the script from here.


 <#  
      .SYNOPSIS  
           Delete Print Jobs for Specific Printer  
        
      .DESCRIPTION  
           This script will delete print jobs for a specific printer. It gets a list of print jobs in the %SYSTEM32\Spool\PRINTERS directory. It reads the contents of the .SHD files, which have the names of the printer inside them. It then filters out those jobs that are not queued for the specified printer and deletes them. It stops the spooler before the deletion and then restarts it.   
        
      .PARAMETER PrinterName  
           Full or partial name of the printer to filter for  
        
      .EXAMPLE  
           Delete all printer jobs for printer named Workshare  
                powershell.exe -file ClearPrintSpooler.ps1 -PrinterName "Workshare"  
   
      .NOTES  
           ===========================================================================  
           Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.135  
           Created on:       2/22/2017 10:46 AM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:          ClearPrintSpooler.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [ValidateNotNullOrEmpty()][string]$PrinterName  
 )  
   
 Clear-Host  
 #Get list of print jobs  
 $Files = Get-ChildItem -Path $env:windir"\system32\spool\printers" -Filter *.SHD  
 #Initialize variable to contain list of files to delete  
 $DeleteFiles = @()  
 foreach ($File in $Files) {  
      #Read contents of binary file  
      $Contents = [System.IO.File]::ReadAllBytes($File.FullName)  
      #Filter out all contents that are not A-Z and a-z in the ASCII number fields  
      $Contents = $Contents | Where-Object { (($_ -ge 65) -and ($_ -le 90)) -or (($_ -ge 97) -and ($_ -le 122)) }  
      #Convert string to ASCII  
      foreach ($Value in $Contents) {  
           $Output += [char]$Value  
      }  
      #Add base file name to the $DeleteFiles list if the $PrinterName is in the converted string  
      If ($Output -like "*$PrinterName*") {  
           $DeleteFiles += $File.BaseName  
      }  
 }  
 #Delete all files that met the searched criteria  
 foreach ($File in $DeleteFiles) {  
      #Stop Print Spooler Service  
      Stop-Service -Name Spooler -Force | Out-Null  
      #Create Filter to search for files  
      $FileFilter = $File + ".*"  
      #Get list of files  
      $Filenames = Get-ChildItem -Path $env:windir"\system32\spool\printers" -Filter $FileFilter  
      #Delete each file  
      foreach ($Filename in $Filenames) {  
           Remove-Item -Path $Filename.FullName -Force | Out-Null  
      }  
      #Start Print Spooler Service  
      Start-Service -Name Spooler | Out-Null  
 }  
   

20 February 2017

Cisco Jabber Conversation Secure Delete Cleanup

Posted By: Mick Pletcher - 12:59 PM















Here is a script that will delete Cisco Jabber conversations. If you are in an environment where you do not want these conversations to be saved and recoverable, this tool with take care of it.

I wrote this tool so that it will kill the CiscoJabber process first and then deletes the appropriate file that stores the conversation. It will then restart Jabber. We set this up to execute when a user logs on and logs off. We have the script setup to make three passes of deletion.

We chose to use SDelete after reading this article on how much better SDelete is at cleaning up files on both SSD and HDD drives. You can download SDelete from here. The script is setup to execute SDelete.exe when it resides in the same location as the script.

Here is a video I took of the script executing. You can see the script closes jabber, deletes the associated .DB file, and then reopens Jabber. It must close Jabber in order to unlock the .DB file for deletion.



I have included examples of executing the script in the script documentation. As you can see in the script, I have set the SecureDeletePasses to 3, which can either be changed or overridden by defining it in the parameter at the time the script is executed.

One more thing about this script was how easy SAPIEN's PowerShell Studio made writing this script. PowerShell Studio takes your script writing abilities to another level as you can see in mine below.

You can download the script from here.


 <#  
      .SYNOPSIS  
           Delete Cisco Jabber Chat History  
        
      .DESCRIPTION  
           Deletes the files and folder that contain the Cisco Jabber chat history  
        
      .PARAMETER SecureDelete  
           Implement Secure Deletion of files and folders  
        
      .PARAMETER SecureDeletePasses  
           Number of secure delete passes  
        
      .EXAMPLE  
           Delete Cisco Jabber chat without secure delete  
                powershell.exe -file CiscoJabberChatCleanup.ps1  
   
           Delete Cisco Jabber chate with secure delete  
                powershell.exe -file CiscoJabberChatCleanup.ps1 -SecureDelete -SecureDeletePasses 3  
   
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.3.131  
           Created on:       12/13/2016 12:20 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:         CiscoJabberChatCleanup.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [switch]$SecureDelete,  
      [string]$SecureDeletePasses = '3'  
 )  
   
 function Close-Process {  
 <#  
      .SYNOPSIS  
           Stop ProcessName  
        
      .DESCRIPTION  
           Kills a ProcessName and verifies it was stopped while reporting it back to the screen.  
        
      .PARAMETER ProcessName  
           Name of ProcessName to kill  
        
      .EXAMPLE  
           PS C:\> Close-ProcessName -ProcessName 'Value1'  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([boolean])]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$ProcessName  
      )  
        
      $Process = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue  
      If ($Process -ne $null) {  
           $Output = "Stopping " + $Process.Name + " process....."  
           Stop-Process -Name $Process.Name -Force -ErrorAction SilentlyContinue  
           Start-Sleep -Seconds 1  
           $TestProcess = Get-Process $ProcessName -ErrorAction SilentlyContinue  
           If ($TestProcess -eq $null) {  
                $Output += "Success"  
                Write-Host $Output  
                Return $true  
           } else {  
                $Output += "Failed"  
                Write-Host $Output  
                Return $false  
           }  
      } else {  
           Return $true  
      }  
 }  
   
 function Get-Architecture {  
 <#  
      .SYNOPSIS  
           Get-Architecture  
        
      .DESCRIPTION  
           Returns whether the system architecture is 32-bit or 64-bit  
        
      .EXAMPLE  
           Get-Architecture  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $OSArchitecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
      $OSArchitecture = $OSArchitecture.OSArchitecture  
      Return $OSArchitecture  
      #Returns 32-bit or 64-bit  
 }  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Open-Application {  
 <#  
      .SYNOPSIS  
           Open Application  
        
      .DESCRIPTION  
           Opens an applications  
        
      .PARAMETER Executable  
           A description of the Executable parameter.  
        
      .PARAMETER ApplicationName  
           Display Name of the application  
        
      .PARAMETER Process  
           Application Process Name  
        
      .EXAMPLE  
           PS C:\> Open-Application -Executable 'Value1'  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [string]$Executable,  
           [ValidateNotNullOrEmpty()][string]$ApplicationName  
      )  
        
      $Architecture = Get-Architecture  
      $Uninstall = Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"  
      If ($Architecture -eq "64-bit") {  
           $Uninstall += Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"  
      }  
      $InstallLocation = ($Uninstall | ForEach-Object {     Get-ItemProperty $_.PsPath } | Where-Object { $_.DisplayName -eq $ApplicationName }).InstallLocation  
      If ($InstallLocation[$InstallLocation.Length - 1] -ne "\") {  
           $InstallLocation += "\"  
      }  
      $Process = ($Executable.Split("."))[0]  
      $Output = "Opening $ApplicationName....."  
      Start-Process -FilePath $InstallLocation$Executable -ErrorAction SilentlyContinue  
      Start-Sleep -Seconds 5  
      $NewProcess = Get-Process $Process -ErrorAction SilentlyContinue  
      If ($NewProcess -ne $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 function Remove-ChatFiles {  
 <#  
      .SYNOPSIS  
           Delete Jabber Chat Files  
        
      .DESCRIPTION  
           Deletes Jabber chat files located at %USERNAME%\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History and verifies they were deleted  
        
      .EXAMPLE  
           PS C:\> Remove-ChatFiles  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      #$JabberChatHistory = $env:USERPROFILE + '\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History'  
      #Get Jabber Chat history files  
      $ChatHistoryFiles = Get-ChildItem -Path $env:USERPROFILE'\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History' -Filter *.db  
      If ($ChatHistoryFiles -ne $null) {  
           foreach ($File in $ChatHistoryFiles) {  
                $Output = "Deleting " + $File.Name + "....."  
                If ($SecureDelete.IsPresent) {  
                     $RelativePath = Get-RelativePath  
                     $Architecture = Get-Architecture  
                     If ($Architecture -eq "32-bit") {  
                          $sDelete = [char]34 + $RelativePath + "sdelete.exe" + [char]34  
                     } else {  
                          $sDelete = [char]34 + $RelativePath + "sdelete64.exe" + [char]34  
                     }  
                     $Switches = "-accepteula -p" + [char]32 + $SecureDeletePasses + [char]32 + "-q" + [char]32 + [char]34 + $File.FullName + [char]34  
                     $ErrCode = (Start-Process -FilePath $sDelete -ArgumentList $Switches -Wait -PassThru).ExitCode  
                     If (($ErrCode -eq 0) -and ((Test-Path $File.FullName) -eq $false)) {  
                          $Output += "Success"  
                     } else {  
                          $Output += "Failed"  
                     }  
                } else {  
                     Remove-Item -Path $File.FullName -Force | Out-Null  
                     If ((Test-Path $File.FullName) -eq $false) {  
                          $Output += "Success"  
                     } else {  
                          $Output += "Failed"  
                     }  
                }  
                Write-Output $Output  
           }  
      } else {  
           $Output = "No Chat History Present"  
           Write-Output $Output  
      }  
 }  
   
 function Remove-MyJabberFilesFolder {  
 <#  
      .SYNOPSIS  
           Delete MyJabberFiles Folder  
        
      .DESCRIPTION  
           Delete the MyJabberFiles folder stores under %USERNAME%\documents and verifies it was deleted.  
        
      .EXAMPLE  
           PS C:\> Remove-MyJabberFilesFolder  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $MyJabberFilesFolder = Get-Item $env:USERPROFILE'\Documents\MyJabberFiles' -ErrorAction SilentlyContinue  
      If ($MyJabberFilesFolder -ne $null) {  
           $Output = "Deleting " + $MyJabberFilesFolder.Name + "....."  
           Remove-Item -Path $MyJabberFilesFolder -Recurse -Force | Out-Null  
           If ((Test-Path $MyJabberFilesFolder.FullName) -eq $false) {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
           }  
           Write-Output $Output  
      } else {  
           $Output = "No MyJabberFiles folder present"  
           Write-Output $Output  
      }  
 }  
   
 Clear-Host  
 #Kill Cisco Jabber Process  
 $JabberClosed = Close-Process -ProcessName CiscoJabber  
 #Delete .DB files from %USERNAME%\AppData\Local\Cisco\Unified Communications\Jabber\CSF\History  
 Remove-ChatFiles  
 #Delete %USERNAME%\documents\MyJabberFiles directory  
 Remove-MyJabberFilesFolder  
 #Reopen Jabber if it was open  
 If ($JabberClosed -eq $true) {  
      Open-Application -ApplicationName "Cisco Jabber" -Executable CiscoJabber.exe  
 }  
   

17 February 2017

Event 51 Drive Failure Reporting Tool

Posted By: Mick Pletcher - 1:28 PM










Last year, we had a high level executive that started having slowness and extended drive usage. When the help desk evaluated his system, they found it had been writing an event 51 to the event viewer logs. They were able to backup all of his data and build a new system for him before the drive completely failed. You can read more about event 51 here. While writing this script, I used SAPIEN's PowerShell Studio 2017 and it made writing the script a breeze, while also helping to streamline the code and documentation. I cannot say enough good things about this product!

I decided to write a reporting tool for this event so that IT professionals will be aware of this error before a complete failure occurs and there is data loss along with losses in production time.

While writing this script, I decided to make it applicable to admins who may not have SCCM. There are three parameters to call from command line: -SCCM if you want it to report to SCCM, -NetworkShare if you don't have SCCM and want it to report to a network share, and -NetworkSharePath which defines to network share to write to.

If you select -SCCM, the script creates a WMI class named DriveReporting and writes a count of error 51 logs to this WMI class instance. Each time the script is executed, it will delete the WMI class and create a new one so no old information may be left over. In order to get this to report to SCCM, you will need to import the WMI class into the hardware inventory. I have included a parameter called -SCCMImport that will create the WMI class and create an instance of five errors. This can then be imported into the hardware inventory of SCCM. The next time the script is executed, this WMI class will be deleted if no errors are detected. I suggest setting up the script as a package in SCCM to run daily during business hours, as it will likely get the most machines that are online if the environment has a lot of laptops.

For those admins with no SCCM server, you can select -NetworkShare while also defining -NetworkSharePath to write to a log file named <Computer Name>.log with the count of errors inside the log file. If no errors are detected and a log file exists, the script deletes it.

I made this video as a tutorial on using this script:



This is a screenshot I took with all parameters selected, except for the -SCCMImport, which is documented in the video.



You can download the script from here.


 <#  
      .SYNOPSIS  
           SMART Reporting  
        
      .DESCRIPTION  
           This script will query the event viewer logs for event ID 51. Event 51 is generated when a drive is in the beginning stages of failing. This script will is to be deployed to machines to generate a WMI entry if event 51 is read. If no event 51 exists, no WMI entry is generated to be read by SCCM.  
        
      .PARAMETER SCCM  
           Select this switch to write the results to WMI for reporting to SCCM.  
        
      .PARAMETER NetworkShare  
           Select this switch to write the results to a text file located on the specified network share inside a file named after the machine this script was executed on.  
        
      .PARAMETER NetworkSharePath  
           UNC path to write output reporting to  
        
      .PARAMETER SCCMImport  
           This is used to create a fake WMI entry so that it can be imported into SCCM.  
        
      .EXAMPLE  
           Setting up the initial import of the WMI Class to SCCM  
                powershell.exe -file SMARTReporting.ps1 -SCCMImport  
   
           Reporting to SCCM  
                powershell.exe -file SMARTReporting.ps1 -SCCM  
   
           Reporting to a Network Share  
                powershell.exe -file SMARTReporting.ps1 -NetworkShare -NetworkSharePath "\\server\path\Reporting"  
   
      .NOTES  
           ===========================================================================  
           Created with:   SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.127  
           Created on:     8/12/2016 11:02 AM  
           Created by:     Mick Pletcher  
           Organization:  
           Filename:       SMARTReporting.ps1  
           ===========================================================================  
 #>  
 param  
 (  
      [switch]$SCCM,  
      [switch]$NetworkShare,  
      [string]$NetworkSharePath,  
      [switch]$SCCMImport  
 )  
 function Initialize-HardwareInventory {  
 <#  
      .SYNOPSIS  
           Perform Hardware Inventory  
        
      .DESCRIPTION  
           Perform a hardware inventory via the SCCM client to report the WMI entry.  
        
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $Output = "Initiate SCCM Hardware Inventory....."  
      $SMSCli = [wmiclass] "\\localhost\root\ccm:SMS_Client"  
      $ErrCode = ($SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}")).ReturnValue  
      If ($ErrCode -eq $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIClass {  
 <#  
      .SYNOPSIS  
           Create New WMI Class  
        
      .DESCRIPTION  
           This will delete the specified WMI class if it already exists and create/recreate the class.  
        
      .PARAMETER Class  
           A description of the Class parameter.  
        
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$Class  
      )  
        
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If (($WMITest -ne "") -and ($WMITest -ne $null)) {  
           $Output = "Deleting " + $Class + " WMI class....."  
           Remove-WmiObject $Class  
           $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
           If ($WMITest -eq $null) {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
                Exit 1  
           }  
           Write-Output $Output  
      }  
      $Output = "Creating " + $Class + " WMI class....."  
      $newClass = New-Object System.Management.ManagementClass("root\cimv2", [string]::Empty, $null);  
      $newClass["__CLASS"] = $Class;  
      $newClass.Qualifiers.Add("Static", $true)  
      $newClass.Properties.Add("Error51", [System.Management.CimType]::string, $false)  
      $newClass.Properties["Error51"].Qualifiers.Add("key", $true)  
      $newClass.Properties["Error51"].Qualifiers.Add("read", $true)  
      $newClass.Put() | Out-Null  
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -eq $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
           Exit 1  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIInstance {  
 <#  
      .SYNOPSIS  
           Write new instance  
        
      .DESCRIPTION  
           Write a new instance reporting the last time the system was rebooted  
        
      .PARAMETER LastRebootTime  
           Date/time the system was last rebooted  
        
      .PARAMETER Class  
           WMI Class  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$Error51,  
           [ValidateNotNullOrEmpty()][string]$Class  
      )  
        
      $Output = "Writing Error 51 information instance to" + [char]32 + $Class + [char]32 + "class....."  
      $Return = Set-WmiInstance -Class $Class -Arguments @{ Error51 = $Error51 }  
      If ($Return -like "*" + $Error51 + "*") {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 function Remove-WMIClass {  
 <#  
      .SYNOPSIS  
           Delete WMIClass  
        
      .DESCRIPTION  
           Delete the WMI class from system  
        
      .PARAMETER Class  
           Name of WMI class to delete  
        
      .EXAMPLE  
                     PS C:\> Remove-WMIClass  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$Class  
      )  
        
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If (($WMITest -ne "") -and ($WMITest -ne $null)) {  
           $Output = "Deleting " + $Class + " WMI class....."  
           Remove-WmiObject $Class  
           $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
           If ($WMITest -eq $null) {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
                Exit 1  
           }  
           Write-Output $Output  
      }  
 }  
   
 Clear-Host  
 #Retrieve number of times error 51 has been logged in the event viewer logs  
 [int]$Count = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 51 } -ErrorAction SilentlyContinue).Count  
 If ($SCCMImport.IsPresent) {  
      #Create WMI Class  
      New-WMIClass -Class DriveReporting  
      #Write a new WMI instance to the WMI class with a report of how many error 51 events were detected  
      New-WMIInstance -Class DriveReporting -Error51 5  
 } else {  
      If ($Count -gt 0) {  
           $Output = "Event 51 disk error has occurred $Count times."  
           Write-Output $Output  
           #Write error reporting to SCCM  
           If ($SCCM.IsPresent) {  
                #Delete the specified WMI class and recreate it for clean reporting  
                New-WMIClass -Class DriveReporting  
                #Write a new WMI instance to the WMI class with a report of how many error 51 events were detected  
                New-WMIInstance -Class DriveReporting -Error51 $Count  
                #Trigger an SCCM hardware inventory to report the errors to SCCM  
                Initialize-HardwareInventory  
           }  
           #Write error reporting to a network share  
           If ($NetworkShare.IsPresent) {  
                #Add a backslash to the end of the defined network share path if it does not exist  
                If ($NetworkSharePath[$NetworkSharePath.Length - 1] -ne "\") {  
                     $NetworkSharePath += "\"  
                }  
                #Define the log file to write the output to  
                $File = $NetworkSharePath + $env:COMPUTERNAME + ".log"  
                #Delete the log file if it already exists so a clean one will be written to  
                If ((Test-Path $File) -eq $true) {  
                     $Output = "Deleting " + $env:COMPUTERNAME + ".log....."  
                     Remove-Item -Path $File -Force | Out-Null  
                     If ((Test-Path $File) -eq $false) {  
                          $Output += "Success"  
                     } else {  
                          $Output += "Failed"  
                     }  
                     Write-Output $Output  
                }  
                #Create a new log file and write number of event 51 logs to it  
                $Output = "Creating " + $env:COMPUTERNAME + ".log....."  
                New-Item -Path $File -ItemType File -Force | Out-Null  
                Add-Content -Path $File -Value "Event 51 Count: $Count" -Force  
                If ((Test-Path $File) -eq $true) {  
                     $Output += "Success"  
                } else {  
                     $Output += "Failed"  
                }  
                Write-Output $Output  
           }  
      } else {  
           $Output = "No event 51 disk errors detected."  
           Write-Output $Output  
           #Delete the WMI class if it exists on the system since no errors were detected  
           If ($SCCM.IsPresent) {  
                Remove-WMIClass -Class DriveReporting  
           }  
           #Delete log file if it exists since no errors were detected  
           If ($NetworkShare.IsPresent) {  
                If ($NetworkSharePath[$NetworkSharePath.Length - 1] -ne "\") {  
                     $NetworkSharePath += "\"  
                }  
                $File = $NetworkSharePath + $env:COMPUTERNAME + ".log"  
                If ((Test-Path $File) -eq $true) {  
                     $Output = "Deleting " + $env:COMPUTERNAME + ".log....."  
                     Remove-Item -Path $File -Force | Out-Null  
                     If ((Test-Path $File) -eq $false) {  
                          $Output += "Success"  
                     } else {  
                          $Output += "Failed"  
                     }  
                     Write-Output $Output  
                }  
           }  
      }  
 }  
   

14 February 2017

Report Last Reboot Time to SCCM

Posted By: Mick Pletcher - 2:47 PM















We have started switching users over from desktops to laptops. In doing so, we realized that a good number of the laptops have not been rebooted in quite a while. The problem comes from sleep and hibernation mode. The LastBootUpTime property of Win32_OperatingSystem is not an accurate date/time. It considers a bootup if a system comes out of a sleep state, cold boot, or reboot. We were wanting to be able to know when a system is rebooted.

The event viewer logs can tell this by searching for the ID 6006. The 6006 event is generated when a system is shutdown or restarted, which is what I was looking for. You can read more on this event here.  To go one step further in the event the same ID is ever used in the future for other reporting, I included searching for "service was stopped". The query for the event is as follows: 

[string]$LastReboot = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 6006 } -MaxEvents 1 | Where-Object { $_.Message -like "*service was stopped*" }).TimeCreated 

I also reached out to the IT community and polled other admins on how they have tracked reboots. They gave me other events they use which include: 6005 (Last time the log service started), 6009 (MultiprocessorFree log is generated when a system boots up), and 27 (Windows 10 only returns a 0x0 when a system is booting up from a shutdown or restart). I have included these options in the script as parameters to be used at the admin's preference.

To make sure this reporting does not continue adding instances to the WMI class it creates, I included the part to delete and recreate the WMI class each time the script executes. The script will also initiate a hardware inventory to report the data up to SCCM. You will need to import the WMI class into SCCM in order for it to read the reboot information.

NOTE: If the Windows 10 fast startup option is enabled, these logs will not register. (Thanks to Ari Saastamoinen for this info.)

You can download the script from here

 <#  
      .SYNOPSIS  
           Report Last Reboot/Shutdown Time  
        
      .DESCRIPTION  
           This script will query the system logs for the last time the system was shutdown or rebooted. I have included four different logs that can be used for determining the last shutdown. I compiled this list from asking poling admins online on what methods they used to determine the last reboot/shutdown of a system. These methods were the most common responses. It will create a WMI class to record the date/time of the last reboot time. The script will then initiate an SCCM hardware inventory to push the data up to SCCM.  
        
      .PARAMETER EventLogServiceStopped  
           Specifies the use of event ID 6006 which is when the event log service was stopped, thereby signifying a system shutdown.  
        
      .PARAMETER KernelBootType  
           Specifies using the event ID 27 and looking for 'The boot type was 0x0' which is a full shutdown. This is a Windows 10 only feature.  
        
      .PARAMETER MultiprocessorFree  
           Specifies using event ID 6009 that is logged when a system starts up.  
        
      .PARAMETER EventLogServiceStarted  
           Specifies the use of event ID 6005 which is when the event log service was started, thereby signifying a system startup.  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.135  
           Created on:       1/30/2017 1:45 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:         LastRebootTime.ps1  
           ===========================================================================  
 #>  
 param  
 (  
      [switch]$EventLogServiceStopped,  
      [switch]$KernelBootType,  
      [switch]$MultiprocessorFree,  
      [switch]$EventLogServiceStarted  
 )  
 function Initialize-HardwareInventory {  
 <#  
      .SYNOPSIS  
           Perform Hardware Inventory  
        
      .DESCRIPTION  
           Perform a hardware inventory via the SCCM client to report the WMI entry.  
        
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $Output = "Initiate SCCM Hardware Inventory....."  
      $SMSCli = [wmiclass] "\\localhost\root\ccm:SMS_Client"  
      $ErrCode = ($SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}")).ReturnValue  
      If ($ErrCode -eq $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIClass {  
 <#  
      .SYNOPSIS  
           Create New WMI Class  
        
      .DESCRIPTION  
           This will delete the specified WMI class if it already exists and create/recreate the class.  
        
      .PARAMETER Class  
           A description of the Class parameter.  
        
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$Class  
      )  
        
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If (($WMITest -ne "") -and ($WMITest -ne $null)) {  
           $Output = "Deleting " + $Class + " WMI class....."  
           Remove-WmiObject $Class  
           $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
           If ($WMITest -eq $null) {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
                Exit 1  
           }  
           Write-Output $Output  
      }  
      $Output = "Creating " + $Class + " WMI class....."  
      $newClass = New-Object System.Management.ManagementClass("root\cimv2", [string]::Empty, $null);  
      $newClass["__CLASS"] = $Class;  
      $newClass.Qualifiers.Add("Static", $true)  
      $newClass.Properties.Add("LastRebootTime", [System.Management.CimType]::string, $false)  
      $newClass.Properties["LastRebootTime"].Qualifiers.Add("key", $true)  
      $newClass.Properties["LastRebootTime"].Qualifiers.Add("read", $true)  
      $newClass.Put() | Out-Null  
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -eq $null) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
           Exit 1  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIInstance {  
 <#  
      .SYNOPSIS  
           Write new instance  
        
      .DESCRIPTION  
           Write a new instance reporting the last time the system was rebooted  
        
      .PARAMETER LastRebootTime  
           Date/time the system was last rebooted  
        
      .PARAMETER Class  
           WMI Class  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]$LastRebootTime,  
           [ValidateNotNullOrEmpty()][string]$Class  
      )  
        
      $Output = "Writing Last Reboot information instance to" + [char]32 + $Class + [char]32 + "class....."  
      $Return = Set-WmiInstance -Class $Class -Arguments @{ LastRebootTime = $LastRebootTime }  
      If ($Return -like "*" + $LastRebootTime + "*") {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
   
 Clear-Host  
 #Get the log entry of the last time the Event Log service was stopped to determine a reboot  
 If ($KernelBootType.IsPresent) {  
      [string]$LastReboot = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 27 } -MaxEvents 1 | Where-Object { $_.Message -like "*boot type was 0x0*" }).TimeCreated  
 }  
 If ($EventLogServiceStarted.IsPresent) {  
      [string]$LastReboot = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 6005 } -MaxEvents 1 | Where-Object { $_.Message -like "*service was started*" }).TimeCreated  
 }  
 If ($EventLogServiceStopped.IsPresent) {  
      [string]$LastReboot = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 6006 } -MaxEvents 1 | Where-Object { $_.Message -like "*service was stopped*" }).TimeCreated  
 }  
 If ($MultiprocessorFree.IsPresent) {  
      [string]$LastReboot = (Get-WinEvent -FilterHashtable @{ logname = 'system'; ID = 6009 } -MaxEvents 1 | Where-Object { $_.Message -like "*Multiprocessor Free*" }).TimeCreated  
 }  
   
 $Output = "Last reboot/shutdown: " + $LastReboot  
 Write-Output $Output  
 #Delete old WMI Class and create new one  
 New-WMIClass -Class "RebootInfo"  
 #Add last reboot date/time as WMI instance  
 New-WMIInstance -LastRebootTime $LastReboot -Class "RebootInfo"  
 #Initialize SCCM hardware inventory to report information back to SCCM  
 Initialize-HardwareInventory  
   

07 February 2017

ImageX Error while Creating MDT Task Sequence

Posted By: Mick Pletcher - 10:09 PM















While creating an MDT task sequence in SCCM, I encountered the error message: System.IO.FileNotFoundException: Could not find file '\imagex.exe'.


I started Googling the error and the best info I found was to either copy over the imageX.exe or update the distribution point. Neither fix worked. After troubleshooting the issue for a couple of hours, I thought that maybe there was an extension issue between MDT and SCCM, so I removed the extension using the Configure ConfigMgr Integration tool. I then readded the extension using the same tool. This fixed the error message and it now creates an MDT task sequence with no problems.

06 February 2017

Automated Dell Command Update

Posted By: Mick Pletcher - 12:23 PM















While working on implementing the new Windows 10 build, I decided to update the Dell Command | Update process. The old script was still looking at the old DCSU and had many inefficiencies that needed to be updated. I also wanted to add new features to the script for logging purposes. With using SAPIEN's PowerShell Studio, the process to update this script was a breeze.

The new script is written so that it can be executed from a command line, build, or a deployment. This now includes the option to write a log file to a specified location by %model%\%ComputerName%. It will delete the old computer name directory and create a new if it already exists. The logs give you the ability to keep track of drivers that may not be applying correctly and you need to inject into SCCM.

Below is how I configured the script in SCCM. The portions I blackened out are the location of the script. It will likely need to be executed as a domain admin account so that it has access to the internet, otherwise you could use the Run PowerShell Script Type.



You can download the script from here.


 <#  
      .SYNOPSIS  
           Update Dell Drivers  
        
      .DESCRIPTION  
           Update Dell drivers using the Dell Command Update.  
        
      .PARAMETER Logging  
           Specifies if logging is to take place  
        
      .PARAMETER LogLocation  
           Location where to write the driver logs  
        
      .NOTES  
           ===========================================================================  
           Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2017 v5.4.135  
           Created on:       2/3/2017 2:21 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:         DellDriverUpdate.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [switch]$Logging,  
      [string]$LogLocation  
 )  
 function Get-Architecture {  
 <#  
      .SYNOPSIS  
           Get-Architecture  
        
      .DESCRIPTION  
           Returns whether the system architecture is 32-bit or 64-bit  
        
      .EXAMPLE  
           Get-Architecture  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $OSArchitecture = (Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture).OSArchitecture  
      Return $OSArchitecture  
      #Returns 32-bit or 64-bit  
 }  
   
 function Get-DellCommandUpdateLocation {  
 <#  
      .SYNOPSIS  
           Find dcu-cli.exe  
        
      .DESCRIPTION  
           Locate dcu-cli.exe as it may reside in %PROGRAMFILES% or %PROGRAMFILES(X86)%  
        
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Architecture = Get-Architecture  
      If ($Architecture -eq "32-bit") {  
           $File = Get-ChildItem -Path $env:ProgramFiles -Filter "dcu-cli.exe" -ErrorAction SilentlyContinue -Recurse  
      } else {  
           $File = Get-ChildItem -Path ${env:ProgramFiles(x86)} -Filter "dcu-cli.exe" -ErrorAction SilentlyContinue -Recurse  
      }  
      Return $File.FullName  
 }  
   
 function Invoke-DriverUpdate {  
 <#  
      .SYNOPSIS  
           Execute Dell Command Update  
        
      .DESCRIPTION  
           This will initiate the Dell Command Update using the dcu-cli.exe  
        
      .PARAMETER Executable  
           dcu-cli.exe  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()]$Executable  
      )  
        
      If ($Logging.IsPresent) {  
           $Model = (Get-WmiObject -Class Win32_ComputerSystem).Model  
           If ($LogLocation[$LogLocation.Length - 1] -ne "\") {  
                $Location = $LogLocation + "\" + $Model  
           } else {  
                $Location = $LogLocation + $Model  
           }  
           If ((Test-Path $LogLocation) -eq $false) {  
                New-Item -Path $LogLocation -ItemType Directory -Force | Out-Null  
           }  
           If ((Test-Path $Location) -eq $false) {  
                New-Item -Path $Location -ItemType Directory -Force | Out-Null  
           }  
           $Location += "\" + $env:COMPUTERNAME  
           If ((Test-Path $Location) -eq $true) {  
                Remove-Item -Path $Location -Recurse -Force  
           }  
           $Arguments = "/log" + [char]32 + [char]34 + $Location + [char]34  
      } else {  
           $Arguments = [char]32  
      }  
      Start-Process -FilePath $Executable -ArgumentList $Arguments -Wait -Passthru | Out-Null  
 }  
   
   
 Clear-Host  
 #Find dcu-cli.exe  
 $EXE = Get-DellCommandUpdateLocation  
 #Install Dell drivers  
 Invoke-DriverUpdate -Executable $EXE  
   

01 February 2017

Using Registry Keys to configure MSI Installations

Posted By: Mick Pletcher - 2:52 PM














After posting the video on how to use registry keys to pre-configure the settings of an MSI during installation, I got a lot of interest, so I decided to create this blog to explain how it works and why it can be beneficial. This method should not be considered as a standard use. It has a very small window of uses in deployments.

Some apps that I deploy are updated on a continuous basis and use MSI installers. The companies never change the MSI properties. Because of never changing the properties in the MSI database tables, this gives the opportunity to create a PowerShell installer script that pushes the registry keys down to the system before it installs the application. Those registry keys control the way the MSI functions during installation the same way calling properties at the msiexec.exe command line does. If you have apps that rarely change the properties of the MSI, this way can make future upgrades a lot easier.

Here is the video I posted on YouTube:



05 January 2017

PowerShell: Cached Exchange Mode Status Reporting

Posted By: Mick Pletcher - 11:05 AM















Recently, the firm I work at is going to cached exchange mode. Due to the sensitive nature of the industry I work in, we are turning on cached mode in blocks of users instead of all at once. We wanted to be able to track what machines have cached exchange mode turned on and what machines have it turned off. PowerShell and SCCM are the best method I devised.

I decided to write this PowerShell script in which it will report back to SCCM via WMI the status of cached exchange mode. It will iterate through all all HKEY_USER profiles to get the status of the exchange mailboxes and reports each to SCCM. The script will create a new WMI entry to write the status of cached exchange mode. In order for this to report to SCCM, you will need to import the WMI entry into the SCCM hardware inventory under Default Client Settings.

While writing this script, I decided to make it an option to either write the output to SCCM or to a text file for those admins that do not have SCCM. You can select a centralized location to write the text files to, which are named <ComputerName>.log with the status written inside the log.

The firm I am at skipped Office 2013, so I was not able to get the location and values of those keys. If you are wanting to use this with Office 2013, you will need to edit the Get-OfficeVersion and Find-RegistryKey functions to add those keys.

I have included examples on how to setup the script to execute at commandline within the script's .Example area. One more thing, SAPIEN's PowerShell Studio made writing this script a breeze! If it was not for PowerShell Studio, the script would not have the depth documentation it does.

This is what the report looked like in the SCCM Queries:


You will likely see some profiles saying unknown. This is caused by profiles that might have 2013 or Office 365 installed.

You can download the script from my GitHub site here.

CachedMode.ps1


1:  <#  
2:       .SYNOPSIS  
3:            Cached Exchange Mode Report  
4:         
5:       .DESCRIPTION  
6:            This script is to be executed on a machine to report if Microsoft Outlook is in cached exchange mode or not. It will report a status of on/off/unknown. The status was tracked down to registry key 00036601. The script queries under HKEY_USERS\%SID%\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles to find which key contains 00036601. The key under the above listed key is the name of the outlook profile and the key that contains 00036601 is a GUID key. Neither of those are standard across different systems, so the script has to find the actual key path. The script can then write the data to either a log file located at a centralized network path, or it can write it to the WMI so that it can be reported back to SCCM. The script was written so that it can be used in an environment that either has SCCM or does not. This script has not been written for Office 365 or Office 2013 as the firm I work at never used that version. If you need include Office 2013, you will need to add Office 15 to the Find-Registry Function  
7:         
8:       .PARAMETER SCCM  
9:            Write output to WMI for reporting to SCCM  
10:         
11:       .PARAMETER TextFile  
12:            Write output to a text file stored at a centralized repository  
13:         
14:       .PARAMETER TextFileLocation  
15:            Location to write the text file to  
16:         
17:       .EXAMPLE  
18:            Write output to WMI entry for reporting to SCCM  
19:                 powershell.exe -file CachedMode.ps1 -SCCM  
20:    
21:            Write output to a text file at a centralized location NOTE: -TextFileLocation can be prepopulated  
22:                 powershell.exe -file CachedMode.ps1 -TextFile -TextFileLocation '\\mick\Systems'  
23:    
24:       .NOTES  
25:            ===========================================================================  
26:            Created with:     SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.3.131  
27:            Created on:       12/29/2016 12:45 PM  
28:            Created by:       Mick Pletcher  
29:            Organization:  
30:            Filename:         CachedModeReporting.ps1  
31:            ===========================================================================  
32:  #>  
33:  [CmdletBinding()]  
34:  param  
35:  (  
36:       [Switch]$SCCM,  
37:       [switch]$TextFile,  
38:       [string]$TextFileLocation  
39:  )  
40:    
41:  function Find-RegistryKey {  
42:  <#  
43:       .SYNOPSIS  
44:            Find Registry Key Value  
45:         
46:       .DESCRIPTION  
47:            Find the registry key that contains the specified value entry  
48:         
49:       .PARAMETER Value  
50:            Value to search registry key for  
51:         
52:       .PARAMETER SID  
53:            HKEY_USERS SID  
54:         
55:       .EXAMPLE  
56:            PS C:\> Find-RegistryKey  
57:         
58:       .NOTES  
59:            Additional information about the function.  
60:  #>  
61:         
62:       [CmdletBinding()][OutputType([string])]  
63:       param  
64:       (  
65:            [ValidateNotNullOrEmpty()][string]$Value,  
66:            [ValidateNotNullOrEmpty()][string]$SID  
67:       )  
68:         
69:       $Version = Get-OfficeVersion  
70:       switch ($Version) {  
71:            "Office 14" { $Key = "HKEY_USERS\" + $SID + "\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles" }  
72:            "Office 16" { $Key = "HKEY_USERS\" + $SID + "\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles" }  
73:       }  
74:       If ((Test-Path REGISTRY::$Key) -eq $true) {  
75:            [string]$CachedMode = get-childitem REGISTRY::$Key -recurse -ErrorAction SilentlyContinue | where-object { $_.property -eq "00036601" }  
76:            If ($CachedMode -ne $null) {  
77:                 [string]$CachedModeValue = (Get-ItemProperty REGISTRY::$CachedMode).'00036601'  
78:                 switch ($Version) {  
79:                      "Office 14" {  
80:                           switch ($CachedModeValue) {  
81:                                #Values below are converted to decimal from the registry hex value commented to the right  
82:                                '128 25 0 0' { Return "Enabled" } #'80 19 0 0'  
83:                                '0 16 0 0' { Return "Disabled" } #'0 10 0 0'  
84:                                default { Return "Unknown" }  
85:                           }  
86:                      }  
87:                      "Office 16" {  
88:                           switch ($CachedModeValue) {  
89:                                #Values below are converted to decimal from the registry hex value commented to the right  
90:                                '132 25 0 0' { Return "Enabled" } #'84 19 0 0'  
91:                                '4 16 0 0' { Return "Disabled" } #'4 10 0 0'  
92:                                default { Return "Unknown" }  
93:                           }  
94:                      }  
95:                 }  
96:                 Return $CachedModeValue  
97:            } else {  
98:                 Return $null  
99:            }  
100:       } else {  
101:            Return $null  
102:       }  
103:  }  
104:    
105:  function Get-HKEY_USERS_List {  
106:  <#  
107:       .SYNOPSIS  
108:            Retrieve list of HKEY_Users  
109:         
110:       .DESCRIPTION  
111:            Retrieve list of HKEY_Users while excluding the built-in and administrator accounts  
112:         
113:       .EXAMPLE  
114:                      PS C:\> Get-HKEY_USERS_List  
115:         
116:       .NOTES  
117:            Additional information about the function.  
118:  #>  
119:         
120:       [CmdletBinding()][OutputType([array])]  
121:       param ()  
122:         
123:       #Get list of HKEY_USERS registry keys filtering out built-in users  
124:       $HKEY_USERS = Get-ChildItem REGISTRY::HKEY_USERS | where-object { ($_.Name -like "*S-1-5-21*") -and ($_.Name -notlike "*_Classes") }  
125:       $Users = @()  
126:       foreach ($User in $HKEY_USERS) {  
127:            #Get the SID of the first profile  
128:            $PROFILESID = Get-ChildItem REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_.name -like "*" + $USER.PSChildName + "*" }  
129:            $SID = $PROFILESID.PSChildName  
130:            #Determine if cached mode is on or off  
131:            $CachedMode = Find-RegistryKey -Value "00036601" -SID $SID  
132:            If ($CachedMode -ne $null) {  
133:                 #Get the username associated with the SID  
134:                 $ProfileName = ((Get-ItemProperty REGISTRY::$PROFILESID).ProfileImagePath).Split("\")[2]  
135:                 #Write username and sid to object  
136:                 $SystemInfo = New-Object -TypeName System.Management.Automation.PSObject  
137:                 Add-Member -InputObject $SystemInfo -MemberType NoteProperty -Name Profile -Value $ProfileName  
138:                 Add-Member -InputObject $SystemInfo -MemberType NoteProperty -Name Status -Value $CachedMode  
139:                 $Users += $SystemInfo  
140:            }  
141:       }  
142:       Return $Users  
143:  }  
144:    
145:  function New-WMIClass {  
146:       [CmdletBinding()]  
147:       param  
148:       (  
149:            [ValidateNotNullOrEmpty()][string]$Class  
150:       )  
151:         
152:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
153:       If ($WMITest -ne "") {  
154:            $Output = "Deleting " + $Class + " WMI class....."  
155:            Remove-WmiObject $Class  
156:            $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
157:            If ($WMITest -eq $null) {  
158:                 $Output += "Success"  
159:            } else {  
160:                 $Output += "Failed"  
161:                 Exit 1  
162:            }  
163:            Write-Output $Output  
164:       }  
165:       $Output = "Creating " + $Class + " WMI class....."  
166:       $newClass = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);  
167:       $newClass["__CLASS"] = $Class;  
168:       $newClass.Qualifiers.Add("Static", $true)  
169:       $newClass.Properties.Add("Profile", [System.Management.CimType]::String, $false)  
170:       $newClass.Properties["Profile"].Qualifiers.Add("key", $true)  
171:       $newClass.Properties["Profile"].Qualifiers.Add("read", $true)  
172:       $newClass.Properties.Add("Status", [System.Management.CimType]::String, $false)  
173:       $newClass.Properties["Status"].Qualifiers.Add("key", $true)  
174:       $newClass.Properties["Status"].Qualifiers.Add("read", $true)  
175:       $newClass.Put() | Out-Null  
176:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
177:       If ($WMITest -eq $null) {  
178:            $Output += "Success"  
179:       } else {  
180:            $Output += "Failed"  
181:            Exit 1  
182:       }  
183:       Write-Output $Output  
184:  }  
185:    
186:  function New-WMIInstance {  
187:  <#  
188:       .SYNOPSIS  
189:            Write new instance  
190:         
191:       .DESCRIPTION  
192:            Write a new instance for each profile along with its cached mode status  
193:         
194:       .PARAMETER Username  
195:            Username  
196:         
197:       .PARAMETER CachedModeStatus  
198:            Status of exchange cached mode  
199:         
200:       .PARAMETER Class  
201:            WMI Class to write information to  
202:         
203:       .PARAMETER MappedDrives  
204:            List of mapped drives  
205:         
206:       .EXAMPLE  
207:            PS C:\> New-WMIInstance  
208:         
209:       .NOTES  
210:            Additional information about the function.  
211:  #>  
212:         
213:       [CmdletBinding()]  
214:       param  
215:       (  
216:            [ValidateNotNullOrEmpty()][string]$Username,  
217:            [ValidateNotNullOrEmpty()][string]$CachedModeStatus,  
218:            [ValidateNotNullOrEmpty()][string]$Class  
219:       )  
220:         
221:       $Output = "Writing Cached Exchange information instance to" + [char]32 + $Class + [char]32 + "class....."  
222:       $Return = Set-WmiInstance -Class $Class -Arguments @{ Profile = $Username; Status = $CachedModeStatus }  
223:       If ($Return -like "*" + $Username + "*") {  
224:            $Output += "Success"  
225:       } else {  
226:            $Output += "Failed"  
227:       }  
228:       Write-Output $Output  
229:  }  
230:    
231:  function Get-OfficeVersion {  
232:  <#  
233:       .SYNOPSIS  
234:            Get Microsoft Office Version  
235:         
236:       .DESCRIPTION  
237:            Execute the OSPP.vbs to display the license information, which also contains the current version of Microsoft Office.  
238:         
239:       .EXAMPLE  
240:                      PS C:\> Get-OfficeVersion  
241:         
242:       .NOTES  
243:            Additional information about the function.  
244:  #>  
245:         
246:       [CmdletBinding()][OutputType([string])]  
247:       param ()  
248:         
249:       If ((Test-Path $env:ProgramFiles"\Microsoft Office") -eq $true) {  
250:            $File = get-childitem -path $env:ProgramFiles"\Microsoft Office" -filter ospp.vbs -recurse  
251:       }  
252:       If ((Test-Path ${env:ProgramFiles(x86)}"\Microsoft Office") -eq $true) {  
253:            $File = get-childitem -path ${env:ProgramFiles(x86)}"\Microsoft Office" -filter ospp.vbs -recurse  
254:       }  
255:       #Get current version of office  
256:       $Version = (cscript.exe $File.Fullname /dstatus | where-object { $_ -like "LICENSE NAME:*" }).split(":")[1].Trim().Split(",")[0]  
257:       Return $Version  
258:  }  
259:    
260:  function Initialize-HardwareInventory {  
261:  <#  
262:       .SYNOPSIS  
263:            Perform Hardware Inventory  
264:         
265:       .DESCRIPTION  
266:            Perform a hardware inventory via the SCCM client to report the WMI entry.  
267:         
268:       .EXAMPLE  
269:                      PS C:\> Initialize-HardwareInventory  
270:         
271:       .NOTES  
272:            Additional information about the function.  
273:  #>  
274:         
275:       [CmdletBinding()]  
276:       param ()  
277:         
278:       $Output = "Initiate SCCM Hardware Inventory....."  
279:       $SMSCli = [wmiclass] "\\localhost\root\ccm:SMS_Client"  
280:       $ErrCode = ($SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}")).ReturnValue  
281:       If ($ErrCode -eq $null) {  
282:            $Output += "Success"  
283:       } else {  
284:            $Output += "Failed"  
285:       }  
286:       Write-Output $Output  
287:  }  
288:    
289:  Clear-Host  
290:  #Get list of users and report if they are running Outlook in cached exchange mode  
291:  $Users = Get-HKEY_USERS_List  
292:  If ($SCCM.IsPresent) {  
293:       #Create new WMI Class to report status to  
294:       New-WMIClass -Class "Cached_Exchange_Mode"  
295:       #Write Cached Mode Status reports to WMI instances  
296:       foreach ($User in $Users) {  
297:            New-WMIInstance -Username $User.Profile -CachedModeStatus $User.Status -Class "Cached_Exchange_Mode"  
298:       }  
299:       Initialize-HardwareInventory  
300:  }  
301:  If ($TextFile.IsPresent) {  
302:       #Check if $TextFileLocation is populated  
303:       If (($TextFileLocation -ne "") -and ($TextFileLocation -ne $null)) {  
304:            #Check if $TextFileLocation exists  
305:            If ((Test-Path $TextFileLocation) -eq $true) {  
306:                 #Insert backslash at the end of the $TextFileLocation and define the name of the text file  
307:                 If ($TextFileLocation.Length - 1 -ne '\') {  
308:                      $File = $TextFileLocation + '\' + $env:COMPUTERNAME + ".log"  
309:                 } else {  
310:                      $File = $TextFileLocation + $env:COMPUTERNAME + ".log"  
311:                 }  
312:                 #Delete the old log file if it exists  
313:                 If ((Test-Path $File) -eq $true) {  
314:                      Remove-Item $File -Force  
315:                 }  
316:                 #Write the results to the log file  
317:                 $Users | Out-File $File -Encoding UTF8 -Force  
318:            } else {  
319:                 Write-Host "Text file location does not exist"  
320:            }  
321:       } else {  
322:            Write-Host "No text file location was defined."  
323:       }  
324:  }  
325:  #Display Results to Screen  
326:  $Users  

11 November 2016

SCCM: Local Administrators Reporting

Posted By: Mick Pletcher - 3:51 PM










Here is a script that will gather a list of local administrators on a machine. The script can report the list to SCCM by writing the list to a WMI entry. It can also write the list to a text file at a specified location for admins that do not have SCCM. The text file is named <%COMPUTERNAME%>.txt. You can do both SCCM reporting and text file reporting if desired.

To implement this into SCCM, run the script once on a machine with the following command line:

powershell.exe -file LocalAdmins.ps1 -SCCMReporting

Once this has executed, do the following: 
  1. Open the SCCM console
  2. Click on Administration
  3. Click Client Settings
  4. Right-click Default Client Settings
  5. Left-click Properties
  6. Click Hardware Inventory
  7. Click Set Classes
  8. Click Add
  9. Click Connect
  10. Enter the computer name of the system you ran the script on
  11. Check Recursive
  12. Check Credentials required
  13. Enter the domain\username for user name
  14. Enter the associated password
  15. Click Connect
  16. Once the list of classes appears, click on Class Name to sort the classes
  17. Scroll down to find Local_Administrators and check the box to the left
  18. Click OK
  19. Click OK
  20. Click OK
  21. Now go back to the machine you ran the script on and run a hardware inventory to send the data up to SCCM. It will take a few minutes until the data appears in SCCM

The next step is to setup the script to execute through SCCM as a package. The script will need to be executed on a routine basis if you want it to be reported regularly to SCCM. As a package, the following pictures show how I have it configured in SCCM.



Finally, you will want to be able to look at the results. You can create a query to show the systems that have reported users in the local administrators group. Here is the WQL I use:


 select distinct SMS_R_System.Name, SMS_G_System_LOCAL_ADMINISTRATORS.Domain, SMS_G_System_LOCAL_ADMINISTRATORS.User from SMS_R_System inner join SMS_G_System_LOCAL_ADMINISTRATORS on SMS_G_System_LOCAL_ADMINISTRATORS.ResourceID = SMS_R_System.ResourceId order by SMS_R_System.Name, SMS_G_System_LOCAL_ADMINISTRATORS.Domain, SMS_G_System_LOCAL_ADMINISTRATORS.User  

You can download the SCCM WQL Query from here.

You can download the script from my GitHub repository located here


LocalAdmins.ps1

 <#  
      .SYNOPSIS  
           Report Local administrators  
        
      .DESCRIPTION  
           Report a list of local administrators on machines to a designated text file, screen, and/or to SCCM via a WMI entry.  
        
      .PARAMETER MemberExclusionsFile  
           Text file containing a list of users to exclude  
        
      .PARAMETER OutputFile  
           Specifies if the output is to be written to a text file. The OutputFileLocation parameter also needs to be populated with the location to write the text file to.  
        
      .PARAMETER OutputFileLocation  
           Location where to write the output text files  
        
      .PARAMETER SCCMReporting  
           Report results to SCCM  
        
      .PARAMETER SystemExclusionsFile  
           Text file containing a list of systems to not generate a report on  
        
      .EXAMPLE  
           Get a list of local admins without reporting to SCCM or writing output to text file  
           powershell.exe -file LocalAdmins.ps1  
             
           Get a list of local admins and report to SCCM  
           powershell.exe -file LocalAdmins.ps1 -SCCMReporting  
             
           Get a list of local admins and write report to a text file at a specified location  
           powershell.exe -file LocalAdmins.ps1 -OutputFile  
        
      .NOTES  
           ===========================================================================  
           Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.129  
           Created on:       11/9/2016 12:47 PM  
           Created by:       Mick Pletcher  
           Organization:  
           Filename:        LocalAdministrators.ps1  
           ===========================================================================  
 #>  
 [CmdletBinding()]  
 param  
 (  
      [string]  
      $MemberExclusionsFile = 'MemberExclusions.txt',  
      [switch]  
      $OutputFile,  
      [string]  
      $OutputFileLocation = '',  
      [switch]  
      $SCCMReporting,  
      [string]  
      $SystemExclusionsFile = 'SystemExclusions.txt'  
 )  
   
 function Get-RelativePath {  
 <#  
      .SYNOPSIS  
           Get the relative path  
        
      .DESCRIPTION  
           Returns the location of the currently running PowerShell script  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()][OutputType([string])]  
      param ()  
        
      $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $Path  
 }  
   
 function Invoke-SCCMHardwareInventory {  
 <#  
      .SYNOPSIS  
           Initiate a Hardware Inventory  
        
      .DESCRIPTION  
           This will initiate a hardware inventory that does not include a full hardware inventory. This is enought to collect the WMI data.  
        
      .EXAMPLE  
                     PS C:\> Invoke-SCCMHardwareInventory  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param ()  
        
      $ComputerName = $env:COMPUTERNAME  
      $SMSCli = [wmiclass] "\\$ComputerName\root\ccm:SMS_Client"  
      $SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}") | Out-Null  
 }  
   
 function New-WMIClass {  
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][string]  
           $Class  
      )  
        
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -ne $null) {  
           $Output = "Deleting " + $Class + " WMI class....."  
           Remove-WmiObject $Class  
           $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
           If ($WMITest -eq $null) {  
                $Output += "success"  
           } else {  
                $Output += "Failed"  
                Exit 1  
           }  
           Write-Output $Output  
      }  
      $Output = "Creating " + $Class + " WMI class....."  
      $newClass = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);  
      $newClass["__CLASS"] = $Class;  
      $newClass.Qualifiers.Add("Static", $true)  
      $newClass.Properties.Add("Domain", [System.Management.CimType]::String, $false)  
      $newClass.Properties["Domain"].Qualifiers.Add("key", $true)  
      $newClass.Properties["Domain"].Qualifiers.Add("read", $true)  
      $newClass.Properties.Add("User", [System.Management.CimType]::String, $false)  
      $newClass.Properties["User"].Qualifiers.Add("key", $false)  
      $newClass.Properties["User"].Qualifiers.Add("read", $true)  
      $newClass.Put() | Out-Null  
      $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
      If ($WMITest -eq $null) {  
           $Output += "success"  
      } else {  
           $Output += "Failed"  
           Exit 1  
      }  
      Write-Output $Output  
 }  
   
 function New-WMIInstance {  
 <#  
      .SYNOPSIS  
           Write new instance  
        
      .DESCRIPTION  
           A detailed description of the New-WMIInstance function.  
        
      .PARAMETER MappedDrives  
           List of mapped drives  
        
      .PARAMETER Class  
           A description of the Class parameter.  
        
      .EXAMPLE  
           PS C:\> New-WMIInstance  
        
      .NOTES  
           Additional information about the function.  
 #>  
        
      [CmdletBinding()]  
      param  
      (  
           [ValidateNotNullOrEmpty()][array]  
           $LocalAdministrators,  
           [string]  
           $Class  
      )  
        
      foreach ($LocalAdministrator in $LocalAdministrators) {  
           $Output = "Writing" + [char]32 +$LocalAdministrator.User + [char]32 + "instance to" + [char]32 + $Class + [char]32 + "class....."  
           $Return = Set-WmiInstance -Class $Class -Arguments @{ Domain = $LocalAdministrator.Domain; User = $LocalAdministrator.User }  
           If ($Return -like "*" + $LocalAdministrator.User + "*") {  
                $Output += "Success"  
           } else {  
                $Output += "Failed"  
           }  
           Write-Output $Output  
      }  
 }  
   
 cls  
 #Get the path this script is being executed from  
 $RelativePath = Get-RelativePath  
 #Name of the computer this script is being executed on  
 $ComputerName = $Env:COMPUTERNAME  
 #Read the list of systems to exclude from reporting  
 $File = $RelativePath + $SystemExclusionsFile  
 $SystemExclusions = Get-Content $File  
 If ($SystemExclusions -notcontains $Env:COMPUTERNAME) {  
      #Get list of users to exclude from reporting  
      $File = $RelativePath + $MemberExclusionsFile  
      $MemberExclusions = Get-Content $File  
      #Get list of local administrators while excluding specified members  
      $Members = net localgroup administrators | Where-Object { $_ -AND $_ -notmatch "command completed successfully" } | select -skip 4 | Where-Object { $MemberExclusions -notcontains $_ }  
      $LocalAdmins = @()  
      foreach ($Member in $Members) {  
           #Create new object  
           $Admin = New-Object -TypeName System.Management.Automation.PSObject  
           $Member = $Member.Split("\")  
           If ($Member.length -gt 1) {  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name Domain -Value $Member[0].Trim()  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name User -Value $Member[1].Trim()  
           } else {  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name Domain -Value ""  
                Add-Member -InputObject $Admin -MemberType NoteProperty -Name User -Value $Member.Trim()  
           }  
           $LocalAdmins += $Admin  
      }  
 }  
 #Report output to WMI which will report up to SCCM  
 If ($SCCMReporting.IsPresent) {  
      New-WMIClass -Class "Local_Administrators"  
      New-WMIInstance -Class "Local_Administrators" -LocalAdministrators $LocalAdmins  
      #Report WMI entry to SCCM  
      Invoke-SCCMHardwareInventory  
 }  
 If ($OutputFile.IsPresent) {  
      If ($OutputFileLocation[$OutputFileLocation.Length - 1] -ne "\") {  
           $File = $OutputFileLocation + "\" + $ComputerName + ".log"  
      } else {  
           $File = $OutputFileLocation + $ComputerName + ".log"  
      }  
      #Delete old log file if it exists  
      $Output = "Deleting $ComputerName.log....."  
      If ((Test-Path $File) -eq $true) {  
           Remove-Item -Path $File -Force  
      }  
      If ((Test-Path $File) -eq $false) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
      $Output = "Writing local admins to $ComputerName.log....."  
      $LocalAdmins | Out-File $File  
      If ((Test-Path $File) -eq $true) {  
           $Output += "Success"  
      } else {  
           $Output += "Failed"  
      }  
      Write-Output $Output  
 }  
 #Display list of local administrators to screen  
 $LocalAdmins  
   

26 October 2016

SCCM Mapped Drives Report

Posted By: Mick Pletcher - 3:10 PM









Recently, we wanted to start keeping track of users with mapped drives due to cryptolocker vulnerabilities. There are a few applications we have that require mapped drives, so we have certain users with them. Once again, I must say thank you to Sapien Technology for PowerShell Studio! That tool makes writing these PowerShell scripts a breeze.

This script will scan all user profiles on a machine and report users with mapped drives. This is done by parsing through the HKU registries. It has been written so that you can either have the script write the report to a text file if you do not have SCCM and/or it can write it to WMI so that SCCM can read the results. I have also included a UNCPathExclusionsFile parameter that allows you to create a text file that resides in the same directory as the script. It contains a list of UNC paths that you do not want the script to report. I recommend pre-populating the values of the $TextFileLocation and $UNCPathExclusionsFile parameters within the script. That just leaves the $OutputFile and $SCCMReporting left to specify at the command line.

If you are wanting this to write the results to SCCM, here is what you need to do. First, SCCM needs to know what to look for in order to report on it. This script will use WMI to report that data to SCCM. The first thing is to execute the script locally on any PC. Run it using the following command line: powershell.exe -file MappedDriveReport.ps1 -SCCMReporting



That command line will execute the script to scan for mapped drives write the results to WMI and then initiate a hardware inventory. Because the new WMI entry has not been added to SCCM, it will not be reported yet. Now that you have executed the script on the local machine, do the following:

  1. Go into SCCM--->Administration Tab--->Client Settings---> Default Client Settings--->Hardware Inventory--->Set Classes.
  2. Click Add--->Connect.
  3. Enter the computer name of the system you ran the script on, check recursive, check Credentials required (Computer is not local)---> <domain>\<username> in the username field, and finally the password for the associated username. 
  4. Click Connect
  5. Click on the Class Name tab to sort by class name
  6. Scroll down to find MappedDrives and check the box
  7. Click OK

You have now added the WMI class to SCCM for it to grab the data from the PCs and report it back to SCCM. 


To get the systems to report the data back to SCCM, you will need to setup a package, not an application, in SCCM to deploy out to the systems. I have the package setup to re-run once a week at 12:00 pm on Wednesdays so that I can get the most users to report back. More users are online at that time here than any of the other days.

If you read the .Example in the documentation portion of the script, you will see two examples on how to execute the script.

I have also included a hardware inventory within the script so the data will be reported back to SCCM right after the script is executed.

In order to view the data in SCCM, you can do the following using the Resource Explorer:

  1. Right-click on a machine in the Assets and Compliance--->Devices
  2. Click Start--->Resource Explorer
  3. Click the plus beside Hardware
  4. If a system had mapped drives, then there will be a mapped drives field, otherwise it does not exist.
You can also use the queries to report systems with mapped drives. Here is the query I use:
select distinct SMS_G_System_MAPPEDDRIVES.user, SMS_G_System_MAPPEDDRIVES.Letter, SMS_G_System_MAPPEDDRIVES.Path from  SMS_R_System inner join SMS_G_System_MAPPEDDRIVES on SMS_G_System_MAPPEDDRIVES.ResourceID = SMS_R_System.ResourceId order by SMS_G_System_MAPPEDDRIVES.user

If you do not have SCCM and need a report, you can use the -OutputFile to have it write the results to a text file at the specified location defined in the $TextFileLocation parameter. 


The script is available on my GitHub site located here.

MappedDriveReport.ps1
1:  <#  
2:       .SYNOPSIS  
3:            Get List of Mapped Drives  
4:         
5:       .DESCRIPTION  
6:            Scans each profile for a list of mapped drives. It will generate a screen report and can also write the output to the WMI for reporting to SCCM.  
7:         
8:       .PARAMETER OutputFile  
9:            Specifies if the output is to be written to a text file. The TextFileLocation parameter also needs to be populated with the location to write the text file to.  
10:         
11:       .PARAMETER TextFileLocation  
12:            Location where to write the text file to  
13:         
14:       .PARAMETER UNCPathExclusionsFile  
15:            Text file containing a list of UNC paths to exclude from reporting.  
16:         
17:       .PARAMETER SCCMReporting  
18:            Specifies to write the data to WMI so that SCCM can pickup the data.  
19:         
20:       .PARAMETER TextFileName  
21:            Write output to a text file  
22:    
23:       .EXAMPLE  
24:            Execute and write output to the reporting file location and also write output to WMI for reporting to SCCM. -TextFileLocation parameter is prepopulated below.  
25:                 powershell.exe -file MappedDriveReport.ps1 -OutputFile -SCCMReporting  
26:    
27:            Execute and write output to WMI to report to SCCM.  
28:                 powershell.exe -file MappedDriveReport.ps1 -SCCMReporting  
29:         
30:       .NOTES  
31:            ===========================================================================  
32:            Created with:      SAPIEN Technologies, Inc., PowerShell Studio 2016 v5.2.128  
33:            Created on:       10/7/2016 10:57 AM  
34:            Created by:       Mick Pletcher  
35:            Organization:  
36:            Filename:          MappedDriveReport.ps1  
37:            ===========================================================================  
38:  #>  
39:  [CmdletBinding()]  
40:  param  
41:  (  
42:       [switch]  
43:       $OutputFile,  
44:       [string]  
45:       $TextFileLocation = '\\drfs1\DesktopApplications\ProductionApplications\Waller\MappedDrivesReport\Reports',  
46:       [string]  
47:       $UNCPathExclusionsFile = "\\drfs1\DesktopApplications\ProductionApplications\Waller\MappedDrivesReport\UNCPathExclusions.txt",  
48:       [switch]  
49:       $SCCMReporting  
50:  )  
51:    
52:  function Get-CurrentDate {  
53:  <#  
54:       .SYNOPSIS   
55:            Get the current date and return formatted value   
56:    
57:       .DESCRIPTION   
58:            Return the current date in the following format: mm-dd-yyyy   
59:    
60:       .NOTES   
61:            Additional information about the function.   
62:  #>  
63:         
64:       [CmdletBinding()][OutputType([string])]  
65:       param ()  
66:    
67:       $CurrentDate = Get-Date  
68:       $CurrentDate = $CurrentDate.ToShortDateString()  
69:       $CurrentDate = $CurrentDate -replace "/", "-"  
70:       If ($CurrentDate[2] -ne "-") {  
71:            $CurrentDate = $CurrentDate.Insert(0, "0")  
72:       }  
73:       If ($CurrentDate[5] -ne "-") {  
74:            $CurrentDate = $CurrentDate.Insert(3, "0")  
75:       }  
76:       Return $CurrentDate  
77:  }  
78:    
79:  function Get-MappedDrives {  
80:  <#  
81:       .SYNOPSIS  
82:            Get list of Mapped Drives  
83:         
84:       .DESCRIPTION  
85:            Retrieve a list of mapped drives for each user that has logged onto the machine.  
86:         
87:       .EXAMPLE  
88:            PS C:\> Get-MappedDrives  
89:         
90:       .NOTES  
91:            Additional information about the function.  
92:  #>  
93:         
94:       [CmdletBinding()][OutputType([array])]  
95:         
96:       #Get UNC Exclusions from UNCPathExclusions.txt file  
97:       $UNCExclusions = Get-Content $UNCPathExclusionsFile -Force  
98:       #Get HKEY_Users Registry Keys  
99:       [array]$UserSIDS = (Get-ChildItem -Path REGISTRY::HKEY_Users | Where-Object { ($_ -notlike "*Classes*") -and ($_ -like "*S-1-5-21*") }).Name  
100:       #Get Profiles from HKLM  
101:       [array]$ProfileList = (Get-ChildItem -Path REGISTRY::"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" | Where-Object { $_ -like "*S-1-5-21*" }).Name  
102:       $UserMappedDrives = @()  
103:       #Iterate through each HKEY_USERS profile  
104:       foreach ($UserSID in $UserSIDS) {  
105:            #GET SID only  
106:            [string]$UserSID = $UserSID.Split("\")[1].Trim()  
107:            #Find the userprofile that matches the HKEY_USERS  
108:            [string]$UserPROFILE = $ProfileList | Where-Object { $_ -like "*" + $UserSID + "*" }  
109:            #Get the username associated with the SID  
110:            $Username = ((Get-ItemProperty -Path REGISTRY::$UserPROFILE).ProfileImagePath).Split("\")[2].trim()  
111:            #Define registry path to mapped drives  
112:            [string]$MappedDrives = "HKEY_USERS\" + $UserSID + "\Network"  
113:            #Get list of mapped drives  
114:            [array]$MappedDrives = (Get-ChildItem REGISTRY::$MappedDrives | Select-Object name).name  
115:            foreach ($MappedDrive in $MappedDrives) {  
116:                 $DriveLetter = (Get-ItemProperty -Path REGISTRY::$MappedDrive | select PSChildName).PSChildName  
117:                 $DrivePath = (Get-ItemProperty -Path REGISTRY::$MappedDrive | select RemotePath).RemotePath  
118:                 If ($DrivePath -notin $UNCExclusions) {  
119:                      $Drives = New-Object System.Management.Automation.PSObject  
120:                      $Drives | Add-Member -MemberType NoteProperty -Name ComputerName -Value $env:COMPUTERNAME  
121:                      $Drives | Add-Member -MemberType NoteProperty -Name Username -Value $Username  
122:                      $Drives | Add-Member -MemberType NoteProperty -Name DriveLetter -Value $DriveLetter  
123:                      $Drives | Add-Member -MemberType NoteProperty -Name DrivePath -Value $DrivePath  
124:                      $UserMappedDrives += $Drives  
125:                 }  
126:            }  
127:       }  
128:       Return $UserMappedDrives  
129:  }  
130:    
131:  function Get-RelativePath {  
132:  <#  
133:       .SYNOPSIS  
134:            Get the relative path  
135:         
136:       .DESCRIPTION  
137:            Returns the location of the currently running PowerShell script  
138:         
139:       .NOTES  
140:            Additional information about the function.  
141:  #>  
142:         
143:       [CmdletBinding()][OutputType([string])]  
144:       param ()  
145:         
146:       $Path = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
147:       Return $Path  
148:  }  
149:    
150:  function Invoke-SCCMHardwareInventory {  
151:  <#  
152:       .SYNOPSIS  
153:            Initiate a Hardware Inventory  
154:         
155:       .DESCRIPTION  
156:            This will initiate a hardware inventory that does not include a full hardware inventory. This is enought to collect the WMI data.  
157:         
158:       .EXAMPLE  
159:                      PS C:\> Invoke-SCCMHardwareInventory  
160:         
161:       .NOTES  
162:            Additional information about the function.  
163:  #>  
164:         
165:       [CmdletBinding()]  
166:       param ()  
167:         
168:       $ComputerName = $env:COMPUTERNAME  
169:       $SMSCli = [wmiclass] "\\$ComputerName\root\ccm:SMS_Client"  
170:       $SMSCli.TriggerSchedule("{00000000-0000-0000-0000-000000000001}") | Out-Null  
171:  }  
172:    
173:  function New-WMIClass {  
174:       [CmdletBinding()]  
175:       param  
176:       (  
177:            [ValidateNotNullOrEmpty()][string]  
178:            $Class  
179:       )  
180:         
181:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
182:       If ($WMITest -ne $null) {  
183:            $Output = "Deleting " + $WMITest.__CLASS[0] + " WMI class....."  
184:            Remove-WmiObject $Class  
185:            $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
186:            If ($WMITest -eq $null) {  
187:                 $Output += "success"  
188:            } else {  
189:                 $Output += "Failed"  
190:                 Exit 1  
191:            }  
192:            Write-Output $Output  
193:       }  
194:       $Output = "Creating " + $Class + " WMI class....."  
195:       $newClass = New-Object System.Management.ManagementClass("root\cimv2", [String]::Empty, $null);  
196:       $newClass["__CLASS"] = $Class;  
197:       $newClass.Qualifiers.Add("Static", $true)  
198:       $newClass.Properties.Add("ComputerName", [System.Management.CimType]::String, $false)  
199:       $newClass.Properties["ComputerName"].Qualifiers.Add("key", $true)  
200:       $newClass.Properties["ComputerName"].Qualifiers.Add("read", $true)  
201:       $newClass.Properties.Add("DriveLetter", [System.Management.CimType]::String, $false)  
202:       $newClass.Properties["DriveLetter"].Qualifiers.Add("key", $false)  
203:       $newClass.Properties["DriveLetter"].Qualifiers.Add("read", $true)  
204:       $newClass.Properties.Add("DrivePath", [System.Management.CimType]::String, $false)  
205:       $newClass.Properties["DrivePath"].Qualifiers.Add("key", $false)  
206:       $newClass.Properties["DrivePath"].Qualifiers.Add("read", $true)  
207:       $newClass.Properties.Add("Username", [System.Management.CimType]::String, $false)  
208:       $newClass.Properties["Username"].Qualifiers.Add("key", $false)  
209:       $newClass.Properties["Username"].Qualifiers.Add("read", $true)  
210:       $newClass.Put() | Out-Null  
211:       $WMITest = Get-WmiObject $Class -ErrorAction SilentlyContinue  
212:       If ($WMITest -eq $null) {  
213:            $Output += "success"  
214:       } else {  
215:            $Output += "Failed"  
216:            Exit 1  
217:       }  
218:       Write-Output $Output  
219:  }  
220:    
221:  function New-WMIInstance {  
222:  <#  
223:       .SYNOPSIS  
224:            Write new instance  
225:         
226:       .DESCRIPTION  
227:            A detailed description of the New-WMIInstance function.  
228:         
229:       .PARAMETER MappedDrives  
230:            List of mapped drives  
231:         
232:       .PARAMETER Class  
233:            A description of the Class parameter.  
234:         
235:       .EXAMPLE  
236:            PS C:\> New-WMIInstance  
237:         
238:       .NOTES  
239:            Additional information about the function.  
240:  #>  
241:         
242:       [CmdletBinding()]  
243:       param  
244:       (  
245:            [ValidateNotNullOrEmpty()][array]  
246:            $MappedDrives,  
247:            [string]  
248:            $Class  
249:       )  
250:         
251:       foreach ($MappedDrive in $MappedDrives) {  
252:            Set-WmiInstance -Class $Class -Arguments @{ ComputerName = $MappedDrive.ComputerName; DriveLetter = $MappedDrive.DriveLetter; DrivePath = $MappedDrive.DrivePath; Username = $MappedDrive.Username } | Out-Null  
253:       }  
254:  }  
255:    
256:  function Start-ConfigurationManagerClientScan {  
257:  <#   
258:       .SYNOPSIS   
259:            Initiate Configuration Manager Client Scan   
260:    
261:       .DESCRIPTION   
262:            This will initiate an SCCM action   
263:    
264:       .PARAMETER ScheduleID   
265:            GUID ID of the SCCM action   
266:    
267:       .NOTES   
268:            Additional information about the function.   
269:  #>  
270:    
271:       [CmdletBinding()]  
272:       param  
273:       (  
274:            [ValidateSet('00000000-0000-0000-0000-000000000121', '00000000-0000-0000-0000-000000000003', '00000000-0000-0000-0000-000000000010', '00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-000000000021', '00000000-0000-0000-0000-000000000022', '00000000-0000-0000-0000-000000000002', '00000000-0000-0000-0000-000000000031', '00000000-0000-0000-0000-000000000108', '00000000-0000-0000-0000-000000000113', '00000000-0000-0000-0000-000000000111', '00000000-0000-0000-0000-000000000026', '00000000-0000-0000-0000-000000000027', '00000000-0000-0000-0000-000000000032')]$ScheduleID  
275:       )  
276:    
277:       $WMIPath = "\\" + $env:COMPUTERNAME + "\root\ccm:SMS_Client"  
278:       $SMSwmi = [wmiclass]$WMIPath  
279:       $Action = [char]123 + $ScheduleID + [char]125  
280:       [Void]$SMSwmi.TriggerSchedule($Action)  
281:  }  
282:    
283:  cls  
284:  #Get list of mapped drives for each user  
285:  $UserMappedDrives = Get-MappedDrives  
286:  #Write output to a text file if -OutputFile is specified  
287:  If ($OutputFile.IsPresent) {  
288:       If (($TextFileLocation -ne $null) -and ($TextFileLocation -ne "")) {  
289:            #Add backslash (\) to the end of the TextFileLocation if it is not present  
290:            If ($TextFileLocation[$TextFileLocation.Length - 1] -ne "\") {  
291:                 $TextFileLocation += "\"  
292:            }  
293:            #Write list of mapped drives to the specified text file.  
294:            [string]$OutputFile = [string]$TextFileLocation + $env:COMPUTERNAME + ".txt"  
295:       } else {  
296:            #Get the relative path this script was executed from  
297:            $RelativePath = Get-RelativePath  
298:            $OutputFile = $RelativePath + $env:COMPUTERNAME + ".txt"  
299:       }  
300:       If ((Test-Path $OutputFile) -eq $true) {  
301:            Remove-Item $OutputFile -Force  
302:       }  
303:       If (($UserMappedDrives -ne $null) -and ($UserMappedDrives -ne "")) {  
304:            $UserMappedDrives | Format-Table -AutoSize | Out-File $OutputFile -Width 255  
305:       }  
306:  }  
307:  If ($SCCMReporting.IsPresent) {  
308:       #Create the new WMI class to write the output data to  
309:       New-WMIClass -Class "Mapped_Drives"  
310:       #Write the output data as an instance to the WMI class  
311:       If ($UserMappedDrives -ne $null) {  
312:            New-WMIInstance -MappedDrives $UserMappedDrives -Class "Mapped_Drives"  
313:       }  
314:       #Invoke a hardware inventory to send the data to SCCM  
315:       Invoke-SCCMHardwareInventory  
316:  }  
317:  #Display list of mapped drives for each user  
318:  $UserMappedDrives | Format-Table  
319:    

Copyright © 2013 Mick's IT Blogs™ is a registered trademark.