Mick's IT Blogs

Mick's IT Blogs

Latest Updates

27 July 2015

MSI Analysis Reporting Tool

Posted By: Mick Pletcher - 4:28 PM





Here is a tool I have written that generates a report on all of the variables used for each entry during an MSI installation. I wrote this script to make customizing the installation of MSI files much easier and quicker. The way this script works is that it will install the MSI file and then come back and uninstall it. It will produce a verbose text file which the script will read from to produce the report.

To use this script, just copy it to the same location as the MSI and execute it. If there is more than one MSI within the directory, you will be prompted on which one to execute. The script will then pop up a blank notepad file. This is to populate with all of your test responses during the setup. For each box you type a text entry into, copy and paste that into the notepad file making a single line entry for each text box. 

If you see a folder window, click change and then copy the directory listed. This will then report the variable associated with the folder.

For radio button boxes, you can enter part of the name of the button, or the entire name. Some radio buttons have the ampersand within the name. It is does, this will not detect it, therefor you will need to enter part of the name. 

The features will be automatically detected, so there is no need to enter those into the text file. The selected features will be read from the log file.

In the end, the report will be published on the screen and to the report.txt file. The following data will be in the report: Product Name, Product Code, MSI Filename, Properties, Features, and Buttons.

NOTE: If you are going to execute the script from a command prompt, make sure the command prompt is also residing in the same directory as the script.

Here is an example report.txt file of packaging InterAction



This was the text input file used during the installation that helped generate the above report:
You can download the script from here.


 <#  
      .SYNOPSIS  
           Generate a report on available values in MSI  
        
      .DESCRIPTION  
           This script will install an MSI file and give a report on the variables used within the MSI  
           for each user input field.   
        
      .EXAMPLE  
           powershell.exe -executionpolicy bypass -file MSIAnalyzer.ps1  
   
      .Author  
           Mick Pletcher  
   
      .Date  
           22 July 2015  
        
      .NOTES  
           Additional information about the file.  
 #>  
   
 #Declare Global Variables  
 Set-Variable -Name ApplicationName -Scope Global -Force  
 Set-Variable -Name MSI -Scope Global -Force  
 Set-Variable -Name ProductCode -Scope Global -Force  
   
 function Get-RelativePath {  
      #Declare Local Variables  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent) + "\"  
      Return $RelativePath  
        
      #Cleanup Local Variables  
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 function Get-Architecture {  
      #Declare Local Variables  
      Set-Variable -Name Architecture -Scope Local -Force  
        
      $Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
      $Architecture = $Global:Architecture.OSArchitecture  
      Return $Architecture  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Architecture -Scope Local -Force  
 }  
   
 function ProcessTextFiles {  
      #Declare Local Variables  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      If ((Test-Path -Path $RelativePath"Installer.log") -eq $true) {  
           Remove-Item -Path $RelativePath"Installer.log" -Force  
      }  
      If ((Test-Path -Path $RelativePath"UserInput.txt") -eq $true) {  
           Remove-Item -Path $RelativePath"UserInput.txt" -Force  
      }  
      If ((Test-Path -Path $RelativePath"Report.txt") -eq $true) {  
           Remove-Item -Path $RelativePath"Report.txt" -Force  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 function Get-MSIFile {  
      #Declare Local Variables  
      Set-Variable -Name FileCount -Value 1 -Scope Local -Force  
      Set-Variable -Name MSIFile -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $MSIFile = Get-ChildItem $RelativePath -Filter *.msi  
      Write-Host $MSIFile.Count  
      If ($MSIFile.Count -eq 1) {  
           Return $MSIFile  
      } else {  
           Do {  
                Clear-Host  
                $FileCount = 1  
                Write-Host "Select MSI to process:"  
                foreach ($MSI in $MSIFile) {  
                     Write-Host $FileCount" - "$MSI  
                     $FileCount++  
                }  
                Write-Host  
                Write-Host "Selection:"  
                [int]$input = Read-Host  
           } while (($input -eq $null) -or ($input -eq "") -or ($input -gt $MSIFile.Count) -or (!($input -as [int] -is [int])))  
           $input = $input - 1  
           $MSIFile = $MSIFile[$input]  
           $global:MSI = $RelativePath + $MSIFile  
      }  
                  
      #Cleanup Local Variables  
      Remove-Variable -Name FileCount -Scope Local -Force  
      Remove-Variable -Name MSIFile -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
 }  
   
 function Get-MSIFileInfo {  
      param (  
           [parameter(Mandatory = $true)][IO.FileInfo]  
           $Path,  
           [parameter(Mandatory = $true)][ValidateSet("ProductCode", "ProductVersion", "ProductName")][string]  
           $Property  
      )  
        
      #Declare Local Variables  
      Set-Variable -Name MSIDatabase -Scope Local -Force  
      Set-Variable -Name Query -Scope Local -Force  
      Set-Variable -Name Record -Scope Local -Force  
      Set-Variable -Name Value -Scope Local -Force  
      Set-Variable -Name View -Scope Local -Force  
      Set-Variable -Name WindowsInstaller -Scope Local -Force  
        
      try {  
           $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer  
           $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($Path.FullName, 0))  
           $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"  
           $View = $MSIDatabase.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $MSIDatabase, ($Query))  
           $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)  
           $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)  
           $Value = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)  
           return $Value  
      } catch {  
           Write-Output $_.Exception.Message  
      }  
        
      #Declare Local Variables  
      Remove-Variable -Name MSIDatabase -Scope Local -Force  
      Remove-Variable -Name Query -Scope Local -Force  
      Remove-Variable -Name Record -Scope Local -Force  
      Remove-Variable -Name Value -Scope Local -Force  
      Remove-Variable -Name View -Scope Local -Force  
      Remove-Variable -Name WindowsInstaller -Scope Local -Force  
 }  
   
 function New-UserInputFile {  
      #Declare Local Variables  
      Set-Variable -Name ErrCode -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      If ((Test-Path -Path $RelativePath"UserInput.txt") -eq $true) {  
           Remove-Item -Path $RelativePath"UserInput.txt" -Force  
      }  
      Write-Host "Creating UserInput.txt File....." -NoNewline  
      $ErrCode = New-Item -Path $RelativePath"UserInput.txt" -Type File -Force  
      If ((Test-Path -Path $RelativePath"UserInput.txt") -eq $true) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name ErrCode -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
        
 }  
   
 function Install-MSI {  
      #Declare Local Variables  
      Set-Variable -Name ErrCode -Scope Local -Force  
      Set-Variable -Name Executable -Scope Local -Force  
      Set-Variable -Name MSI -Scope Local -Force  
      Set-Variable -Name Parameters -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name Switches -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $Global:ApplicationName = Get-MSIFileInfo -Path $global:MSI -Property 'ProductName'  
      $Global:ProductCode = Get-MSIFileInfo -Path $global:MSI -Property 'ProductCode'  
      If ((Test-Path -Path $RelativePath"Installer.log") -eq $true) {  
           Remove-Item -Path $RelativePath"Installer.log" -Force  
      }  
      $Executable = $Env:windir + "\system32\msiexec.exe"  
      $Switches = "/qb- /norestart"  
      $Parameters = "/x" + [char]32 + $Global:ProductCode + [char]32 + $Switches  
      $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
      Write-Host "Uninstalling"$Global:ApplicationName"....." -NoNewline  
      if (($ErrCode -eq 0) -or ($ErrCode -eq 3010)) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
      }  
      $Notepad = $env:windir + "\notepad.exe"  
      $Parameters = $RelativePath + "UserInput.txt"  
      $ErrCode = (Start-Process -FilePath $Notepad -ArgumentList $Parameters -PassThru).ExitCode  
      $Switches = "/norestart"  
      $Parameters = "/i " + [char]34 + $Global:MSI + [char]34 + [char]32 + $Switches + [char]32 + "/lvx " + [char]34 + $RelativePath + "Installer.log" + [char]34  
      Write-Host $Parameters  
      Write-Host "Installing"$Global:ApplicationName"....." -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  
      }  
      Write-Host "Creating Log File....." -NoNewline  
      If ((Test-Path $RelativePath"Installer.log") -eq $true) {  
           Write-Host "Success" -ForegroundColor Yellow  
      } else {  
           Write-Host "Failed" -ForegroundColor Red  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name ErrCode -Scope Local -Force  
      Remove-Variable -Name Executable -Scope Local -Force  
      Remove-Variable -Name MSI -Scope Local -Force  
      Remove-Variable -Name Parameters -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name Switches -Scope Local -Force  
 }  
   
 function Uninstall-MSI {  
      #Declare Local Variables  
      Set-Variable -Name ErrCode -Scope Local -Force  
      Set-Variable -Name Executable -Scope Local -Force  
      Set-Variable -Name Line -Scope Local -Force  
      Set-Variable -Name LogFile -Scope Local -Force  
      Set-Variable -Name MSI -Scope Local -Force  
      Set-Variable -Name Parameters -Scope Local -Force  
      Set-Variable -Name Process -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name Switches -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $Process = Get-Process -Name notepad -ErrorAction SilentlyContinue  
      If ($Process -ne $null) {  
           Stop-Process -Name notepad -ErrorAction SilentlyContinue -Force  
      }  
      $Executable = $Env:windir + "\system32\msiexec.exe"  
      $Switches = "/qb- /norestart"  
      $Parameters = "/x" + [char]32 + $Global:ProductCode + [char]32 + $Switches  
      Write-Host "Uninstalling"$Global:ApplicationName"....." -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  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name ErrCode -Scope Local -Force  
      Remove-Variable -Name Executable -Scope Local -Force  
      Remove-Variable -Name Line -Scope Local -Force  
      Remove-Variable -Name LogFile -Scope Local -Force  
      Remove-Variable -Name MSI -Scope Local -Force  
      Remove-Variable -Name Parameters -Scope Local -Force  
      Remove-Variable -Name Process -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name Switches -Scope Local -Force  
 }  
   
 function Get-ProductName {  
      #Declare Local Variables  
      Set-Variable -Name Database -Scope Local -Force  
      Set-Variable -Name MSIFileName -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
      Set-Variable -Name PropertyName -Scope Local -Force  
      Set-Variable -Name PropertyValue -Scope Local -Force  
      Set-Variable -Name Record -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name View -Scope Local -Force  
      Set-Variable -Name WindowsInstaller -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $MSIFileName = $global:MSI  
      $OutputFile = $RelativePath + "Report.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "Product Name"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "------------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $WindowsInstaller = New-Object -com WindowsInstaller.Installer  
      $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($MSIFileName, 0))  
      $View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ("SELECT * FROM Property"))  
      $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)  
      $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
      while ($Record -ne $Null) {  
           $PropertyName = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 1)  
           [string]$PropertyValue = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 2)  
           IF ($PropertyName -like "*ProductName*") {  
                $Output = $PropertyValue  
                Write-Host $Output  
                Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
           }  
           $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Database -Scope Local -Force  
      Remove-Variable -Name MSIFileName -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
      Remove-Variable -Name PropertyName -Scope Local -Force  
      Remove-Variable -Name PropertyValue -Scope Local -Force  
      Remove-Variable -Name Record -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name View -Scope Local -Force  
      Remove-Variable -Name WindowsInstaller -Scope Local -Force  
 }  
   
 function Get-ProductCode {  
      #Declare Local Variables  
      Set-Variable -Name Database -Scope Local -Force  
      Set-Variable -Name MSIFileName -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
      Set-Variable -Name PropertyName -Scope Local -Force  
      Set-Variable -Name PropertyValue -Scope Local -Force  
      Set-Variable -Name Record -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name View -Scope Local -Force  
      Set-Variable -Name WindowsInstaller -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $MSIFileName = $global:MSI  
      $OutputFile = $RelativePath + "Report.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "Product Code"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "------------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $WindowsInstaller = New-Object -com WindowsInstaller.Installer  
      $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($MSIFileName, 0))  
      $View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ("SELECT * FROM Property"))  
      $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)  
      $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
      while ($Record -ne $Null) {  
           $PropertyName = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 1)  
           [string]$PropertyValue = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 2)  
           IF ($PropertyName -like "*ProductCode*") {  
                $Output = $PropertyValue  
                Write-Host $Output  
                Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
           }  
           $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Database -Scope Local -Force  
      Remove-Variable -Name MSIFileName -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
      Remove-Variable -Name PropertyName -Scope Local -Force  
      Remove-Variable -Name PropertyValue -Scope Local -Force  
      Remove-Variable -Name Record -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name View -Scope Local -Force  
      Remove-Variable -Name WindowsInstaller -Scope Local -Force  
 }  
   
 function Get-MSIFileName {  
      #Declare Local Variables  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
        
      $OutputFile = $RelativePath + "Report.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "MSI Filename"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "------------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = $global:MSI  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
        
 }  
   
 function Get-Properties {  
      #Declare Local Variables  
      Set-Variable -Name Entries -Scope Local -Force  
      Set-Variable -Name Entry -Scope Local -Force  
      Set-Variable -Name File -Scope Local -Force  
      Set-Variable -Name FormattedEntry -Scope Local -Force  
      Set-Variable -Name Line -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
      Set-Variable -Name Position -Scope Local -Force  
      Set-Variable -Name Property -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name Value -Scope Local -Force  
        
      $OutputArray = @()  
      $RelativePath = Get-RelativePath  
      $File = Get-Content -Path $RelativePath"Installer.log"  
      $OutputFile = $RelativePath+"Report.txt"  
      $Entries = Get-Content -Path $RelativePath"UserInput.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "Properties"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "----------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      If ($Entries -ne $null) {  
           foreach ($Line in $File) {  
                If ($Line -like "*PROPERTY CHANGE: Adding*") {  
                     foreach ($Entry in $Entries) {  
                          $FormattedEntry = [char]42 + [char]39 + $Entry + [char]39 + [char]42  
                          If ($Line -like $FormattedEntry) {  
                               $Property = $Line  
                               $Value = $Line  
                               $Property = $Property.split(':')[-1]  
                               If ($Property[0] -eq "\") {  
                                    $Property = $Line  
                                    $Property = $Property.split(':')[-2]  
                               }  
                               $Property = $Property.Trim()  
                               $Property = $Property.Trim("Adding")  
                               $Property = $Property.Trim()  
                               $Property = $Property.Trim(" ")  
                               $Position = $Property.IndexOf(" ")  
                               If ($Property -notlike "*:\*") {  
                                    $Property = $Property.Substring(0, $Position)  
                               }  
                               $Output = $Property + ": " + $Entry  
                               $OutputArray += $Output  
                          }  
                     }  
                }  
           }  
           $OutputArray = $OutputArray | select -Unique  
           $OutputArray = $OutputArray | Sort  
           foreach ($Item in $OutputArray) {  
                Write-Host $Item  
                If ($Item -ne $null) {  
                     Out-File -FilePath $OutputFile -InputObject $Item -Append -Force  
                }  
           }  
      } else {  
           $Output = "No User Input Properties Exist"  
           Write-Host $Output  
           Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Entries -Scope Local -Force  
      Remove-Variable -Name Entry -Scope Local -Force  
      Remove-Variable -Name File -Scope Local -Force  
      Remove-Variable -Name FormattedEntry -Scope Local -Force  
      Remove-Variable -Name Line -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
      Remove-Variable -Name Position -Scope Local -Force  
      Remove-Variable -Name Property -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name Value -Scope Local -Force  
 }  
   
 function Get-Features {  
      #Declare Local Variables  
      Set-Variable -Name Entries -Scope Local -Force  
      Set-Variable -Name File -Scope Local -Force  
      Set-Variable -Name Line -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name Value -Scope Local -Force  
      Set-Variable -Name Values -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $OutputFile = $RelativePath + "Report.txt"  
      $Entries = Get-Content -Path $RelativePath"UserInput.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "Features"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "--------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      If ($Entries -ne $null) {  
           $File = Get-Content -Path $global:RelativePath"Installer.log"  
           foreach ($Line in $File) {  
                If ($Line -like "*ADDLOCAL*") {  
                     $Value = $Line.split(' ')[-1]  
                     $Value = $Value -replace '''', ''  
                     $Value = $Value.SubString(0, $Value.Length - 1)  
                     $Values = $Value.Split(",")  
                     foreach ($Value in $Values) {  
                          Write-Host $Value  
                          Out-File -FilePath $OutputFile -InputObject $Value -Append -Force  
                     }  
                }  
           }  
      } else {  
           $Output = "No User Input Features Exist"  
           Write-Host $Output  
           Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Entries -Scope Local -Force  
      Remove-Variable -Name File -Scope Local -Force  
      Remove-Variable -Name Line -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name Value -Scope Local -Force  
      Remove-Variable -Name Values -Scope Local -Force  
 }  
   
 function Get-Buttons {  
      #Declare Local Variables  
      Set-Variable -Name Database -Scope Local -Force  
      Set-Variable -Name Entries -Scope Local -Force  
      Set-Variable -Name Entry -Scope Local -Force  
      Set-Variable -Name MSIFileName -Scope Local -Force  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name OutputFile -Scope Local -Force  
      Set-Variable -Name PropertyName -Scope Local -Force  
      Set-Variable -Name PropertyValue -Scope Local -Force  
      Set-Variable -Name Record -Scope Local -Force  
      Set-Variable -Name RelativePath -Scope Local -Force  
      Set-Variable -Name View -Scope Local -Force  
      Set-Variable -Name WindowsInstaller -Scope Local -Force  
        
      $RelativePath = Get-RelativePath  
      $MSIFileName = $global:MSI  
      $Entries = Get-Content -Path $RelativePath"UserInput.txt"  
      $OutputFile = $RelativePath + "Report.txt"  
      $Output = [char]13  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "Buttons"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      $Output = "--------"  
      Write-Host $Output  
      Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
      foreach ($Entry in $Entries) {  
           $WindowsInstaller = New-Object -com WindowsInstaller.Installer  
           $Database = $WindowsInstaller.GetType().InvokeMember("OpenDatabase", "InvokeMethod", $Null, $WindowsInstaller, @($MSIFileName, 0))  
           $View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $Null, $Database, ("SELECT * FROM RadioButton"))  
           $View.GetType().InvokeMember("Execute", "InvokeMethod", $Null, $View, $Null)  
           $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
           $Entry = "*" + $Entry + "*"  
           while ($Record -ne $Null) {  
                $PropertyName = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 1)  
                if (-not ($PropertyName -cmatch "[a-z]")) {  
                     [string]$PropertyValue = $Record.GetType().InvokeMember("StringData", "GetProperty", $Null, $Record, 8)  
                     IF ($PropertyValue -like $Entry) {  
                          $Output = $PropertyName + " = " + $PropertyValue  
                          Write-Host $Output  
                          Out-File -FilePath $OutputFile -InputObject $Output -Append -Force  
                          #Write-Host ($PropertyName + " = " + $PropertyValue)  
                     }  
                }  
                $Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $Null, $View, $Null)  
           }  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name Database -Scope Local -Force  
      Remove-Variable -Name Entries -Scope Local -Force  
      Remove-Variable -Name Entry -Scope Local -Force  
      Remove-Variable -Name MSIFileName -Scope Local -Force  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name OutputFile -Scope Local -Force  
      Remove-Variable -Name PropertyName -Scope Local -Force  
      Remove-Variable -Name PropertyValue -Scope Local -Force  
      Remove-Variable -Name Record -Scope Local -Force  
      Remove-Variable -Name RelativePath -Scope Local -Force  
      Remove-Variable -Name View -Scope Local -Force  
      Remove-Variable -Name WindowsInstaller -Scope Local -Force  
 }  
   
 Clear-Host  
 ProcessTextFiles  
 Get-MSIFile  
 New-UserInputFile  
 Install-MSI  
 Uninstall-MSI  
 Get-ProductName  
 Get-ProductCode  
 Get-MSIFileName  
 Get-Properties  
 Get-Features  
 Get-Buttons  
   
 #Cleanup Global Variables  
 Remove-Variable -Name ApplicationName -Scope Global -Force  
 Remove-Variable -Name MSI -Scope Global -Force  
 Remove-Variable -Name ProductCode -Scope Global -Force  

16 July 2015

Running Programs as an Application Installation in SCCM 2012

Posted By: Mick Pletcher - 10:16 AM
Recently, I had to deploy an application, IntApp, with minimal intrusion and no reboots. Part of the process was to restart the application after a successful installation. The crux was that the application has to run under the user credentials. The app starts every time a system is logged into, but with laptops, that could have been a long time until the user actually rebooted the machine. This could also have been done as a package in which the executable would have been executed, but I wanted verification that when it was executed, it was actually running. To achieve this, I wrote two powershell scripts. The first one is the detection method and the second is the one that actually executes the app.

The detection method first makes sure the executable is installed. If it is, it then looks for the process to see if it is running. If it's running, a success is returned to SCCM. If not, nothing is returned back to SCCM, therefor SCCM interprets that as a failure and proceeds to execute the installer. If the application is not installed, it also returns a failure to SCCM.

The installation will test to see if the executable is present. If not, it automatically fail the install with an exit code 1. If it is installed, it will then test for the process. If the process is present, it will return a success code back to SCCM. If it is not, it will then run the executable and retest to make sure it is present in memory.

Here is the detection method:


 $DesktopExtension = $Env:ProgramFiles + "\IntApp\Desktop Extension\IntappTimeDesktopExtension.exe"  
 If ((Test-Path -Path $DesktopExtension) -eq $true) {  
      $ProcessActive = Get-Process -Name IntappTimeDesktopExtension -ErrorAction SilentlyContinue  
      If ($ProcessActive.ProcessName -eq "IntappTimeDesktopExtension") {  
           Write-Host "Success"  
           exit 0  
      } else {  
           exit 0  
      }  
 } else {  
      exit 0  
 }  


Here is the application installation script:


 $DesktopExtension = $Env:ProgramFiles + "\IntApp\Desktop Extension\IntappTimeDesktopExtension.exe"  
 If ((Test-Path -Path $DesktopExtension) -eq $true) {  
      $ProcessActive = Get-Process -Name IntappTimeDesktopExtension -ErrorAction SilentlyContinue  
      If ($ProcessActive.ProcessName -ne "IntappTimeDesktopExtension") {  
           Start-Process -FilePath $env:ProgramFiles"\IntApp\Desktop Extension\IntappTimeDesktopExtension.exe" -ErrorAction SilentlyContinue  
           Start-Sleep -Seconds 2  
      } else {  
           Write-Host "Success"  
           exit 0  
      }  
      $ProcessActive = Get-Process -Name IntappTimeDesktopExtension -ErrorAction SilentlyContinue  
      If ($ProcessActive.ProcessName -eq "IntappTimeDesktopExtension") {  
           Write-Host "Success"  
           exit 0  
      } else {  
           exit 0  
      }  
 } else {  
      exit 1  
 }  
   

14 July 2015

PowerShell MSI Uninstaller By Application Name

Posted By: Mick Pletcher - 9:26 AM
Here is a function that will uninstall an MSI installed application by the name of the app. You do not need to input the entire name either. For instance, say you are uninstalling all previous versions of Adobe Reader. Adobe Reader is always labeled Adobe Reader X, Adobe Reader XI, and so forth. You just need to enter Adobe Reader as the application name and the desired switches. It will then search the name fields in the 32 and 64 bit uninstall registry keys to find the associated GUID. Finally, it will execute an msiexec.exe /x {GUID} to uninstall that version.




 Function Uninstall-MSIByName {  
      <#  
      .SYNOPSIS  
           Uninstall-MSIByName  
      .DESCRIPTION  
           Uninstalls an MSI application using the MSI file  
      .EXAMPLE  
           Uninstall-MSIByName -ApplicationName "Adobe Reader" -Switches "/qb- /norestart"  
      #>  
        
      Param ([String]  
           $ApplicationName,  
           [String]  
           $Switches)  
        
      #Declare Local Variables  
      Set-Variable -Name ErrCode -Scope Local -Force  
      Set-Variable -Name Executable -Scope Local -Force  
      Set-Variable -Name Key -Scope Local -Force  
      Set-Variable -Name KeyName -Scope Local -Force  
      Set-Variable -Name Parameters -Scope Local -Force  
      Set-Variable -Name SearchName -Scope Local -Force  
      Set-Variable -Name TempKey -Scope Local -Force  
      Set-Variable -Name Uninstall -Scope Local -Force  
        
      $Uninstall = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
      $Uninstall += Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
      $SearchName = "*" + $ApplicationName + "*"  
      $Executable = $Env:windir + "\system32\msiexec.exe"  
      Foreach ($Key in $Uninstall) {  
           $TempKey = $Key.Name -split "\\"  
           If ($TempKey[002] -eq "Microsoft") {  
                $Key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" + $Key.PSChildName  
           } else {  
                $Key = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\" + $Key.PSChildName  
           }  
           If ((Test-Path $Key) -eq $true) {  
                $KeyName = Get-ItemProperty -Path $Key  
                If ($KeyName.DisplayName -like $SearchName) {  
                     $TempKey = $KeyName.UninstallString -split " "  
                     If ($TempKey[0] -eq "MsiExec.exe") {  
                          Write-Host "Uninstall"$KeyName.DisplayName"....." -NoNewline  
                          $Parameters = "/x " + $KeyName.PSChildName + [char]32 + $Switches  
                          $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
                          If (($ErrCode -eq 0) -or ($ErrCode -eq 3010) -or ($ErrCode -eq 1605)) {  
                               Write-Host "Success" -ForegroundColor Yellow  
                          } else {  
                               Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
                          }  
                     }  
                }  
           }  
      }  
        
      #Cleanup Local Variables  
      Remove-Variable -Name ErrCode -Scope Local -Force  
      Remove-Variable -Name Executable -Scope Local -Force  
      Remove-Variable -Name Key -Scope Local -Force  
      Remove-Variable -Name KeyName -Scope Local -Force  
      Remove-Variable -Name Parameters -Scope Local -Force  
      Remove-Variable -Name SearchName -Scope Local -Force  
      Remove-Variable -Name TempKey -Scope Local -Force  
      Remove-Variable -Name Uninstall -Scope Local -Force  
        
 }  
   

01 July 2015

Laptop Mandatory Reboot Management

Posted By: Mick Pletcher - 5:16 PM
Managing laptops in certain environments can be daunting. Reboots are a must every now and then, especially for monthly windows updates. With the sleep and hibernate features being enabled, the chances of a user rebooting a laptop become far less. A laptop can go weeks and even months without a reboot. Working in the legal industry, as I do, adds to the complexity of forcing reboots as you have the issue of not causing a reboot during a hearing or during a client meeting for instance. You want to be as unobtrusive as possible. You might say that this is not needed as SCCM could be setup to automatically perform this same function on a regular schedule. That is true. Where this becomes valuable is when you don't want to force a reboot on users that regularly reboot their machines. If they are already doing this, which we have a fair number of users that do, then there is no need to reboot an additional time that will inconvenience them.

To make this process as unobtrusive as possible, I have written the following two PowerShell scripts that work in conjunction with SCCM 2012 to give users the leisure of a full business day to reboot the machine. One script is the custom detection method and the other is the installer. 

The custom detection method works by reading the last event viewer 1074. It looks at the date of the ID and then sees if it is 14 days or older. This can be set to any number of days other than the 14 my firm has chosen. If it is 14 days old, the script then sets the registry key Rebooted to a DWORD value of 1 and fails the detection method. When the method fails, SCCM will then run the second PowerShell script. 

The second script operates by running the same detection method. Once it detects the same values, it resets the Rebooted Key to 0 and then returns the error code 3010 back to SCCM. SCCM then reruns the detection method. The detection method sees there has been 14 days or more and the Rebooted key is set to 0. It returns and error code 0 back to SCCM with a write-host of "Success". This tells SCCM the application ran successfully and to now process with the soft reboot, which was required by the 3010 return code. 

The final part is to configure the Computer Restart under the SCCM client settings. I configured ours to be 480 minutes, which is 8 hours, with a mandatory dialog window that cannot be closed the final 60 minutes. 

When the system reboots, the custom detection method runs again and sees there is a new 1074 entry in the event viewer, along with the registry key Rebooted being a 0, therefor it shows successfully installed. As the days progress and SCCM reruns the custom detection method, it will rerun the application script to reboot the machine again if the machine is not rebooted in the 14 allotted days. If the user reboots the machine every week, the SCCM application will never reboot the machine. 

Here are the links to the two scripts:

 <#  
 .SYNOPSIS  
   SCCM Reboot Detection Method  
 .DESCRIPTION  
   This script will read the last time a system rebooted from the event  
   viewer logs. It then calculates the number of days since that time. If  
   the number of days equals or exceeds the RebootThreshold variable, the  
   script will exit with a return code 0 and no data output. No data output   
   is read by SCCM as a failure. If the number of days is less than the  
   RebootThreshold, then a message is written saying the system is within  
   the threshold and the script exits with a return code of 0. SCCM reads  
   an error code 0 with data output as a success.   
 .Author  
   Mick Pletcher  
 .Date  
   30 June 2015  
 #>  
   
 $RebootThreshold = 14  
 $Today = Get-Date  
 $Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
 $Architecture = $Architecture.OSArchitecture  
 $LastReboot = get-winevent -filterhashtable @{logname='system';ID=1074} -maxevents 1 -ErrorAction SilentlyContinue  
 if ($Architecture -eq "32-bit") {  
      if ((Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot") -eq $false) {  
           New-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot" | New-ItemProperty -Name Rebooted -Value 0 -Force | Out-Null  
      }  
      $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot"  
      $Rebooted = $Rebooted.Rebooted  
 } else {  
      if ((Test-Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot") -eq $false) {  
           New-Item "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot" | New-ItemProperty -Name Rebooted -Value 0 -Force | Out-Null  
      }  
      $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot"  
      $Rebooted = $Rebooted.Rebooted  
 }  
 if ($LastReboot -eq $null) {  
      $Difference = $RebootThreshold  
 } else {  
      $Difference = New-TimeSpan -Start $Today -End $LastReboot.TimeCreated  
      $Difference = [math]::Abs($Difference.Days)  
 }  
 #Write-Host "Reboot Threshold:"$RebootThreshold  
 #Write-Host "Difference:"$Difference  
 #Write-Host "Rebooted:"$Rebooted  
 if (($Difference -lt $RebootThreshold) -and ($Rebooted -eq 0)) {  
      Write-Host "Success"  
      exit 0  
 }  
 if (($Difference -ge $RebootThreshold) -and ($Rebooted -eq 1)) {  
      Write-Host "Success"  
      exit 0  
 }  
 if (($Difference -ge $RebootThreshold) -and ($Rebooted -eq 0)) {  
      exit 0  
 }  
 if (($Difference -lt $RebootThreshold) -and ($Rebooted -eq 1)) {  
      exit 0  
 }  
   



 <#  
 .SYNOPSIS  
   SCCM Mandatory Reboot  
 .DESCRIPTION  
   This script will read the last time a system rebooted from the event  
   viewer logs. It then calculates the number of days since that time. If  
   the number of days equals or exceeds the RebootThreshold variable, the  
   script will change the registry key Rebooted to a 0. It then exits with   
   an error code 3010, which tells SCCM 2012 to perform a soft reboot.  
 .Author  
   Mick Pletcher  
 .Date  
   30 June 2015  
 #>  
   
 #Declare Global Variables  
 Set-Variable -Name Architecture -Scope local -Force  
 Set-Variable -Name Difference -Scope Local -Force  
 Set-Variable -Name LastReboot -Scope Local -Force  
 Set-Variable -Name Rebooted -Scope Local -Force  
 Set-Variable -Name RebootThreshold -Scope Local -Force  
 Set-Variable -Name Today -Scope Local -Force  
   
 $Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
 $Architecture = $Architecture.OSArchitecture  
 if ($Architecture -eq "32-bit") {  
      if ((Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot") -eq $false) {  
           New-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot" | New-ItemProperty -Name Rebooted -Value 0 -Force | Out-Null  
      }  
      $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot"  
      $Rebooted = $Rebooted.Rebooted  
 } else {  
      if ((Test-Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot") -eq $false) {  
           New-Item "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot" | New-ItemProperty -Name Rebooted -Value 0 -Force | Out-Null  
      }  
      $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot"  
      $Rebooted = $Rebooted.Rebooted  
 }  
 $RebootThreshold = 14  
 $Today = Get-Date  
 $LastReboot = get-winevent -filterhashtable @{logname='system';ID=1074} -maxevents 1 -ErrorAction SilentlyContinue  
 if ($LastReboot -eq $null) {  
      $Difference = $RebootThreshold 
 } else {  
      $Difference = New-TimeSpan -Start $Today -End $LastReboot.TimeCreated  
      $Difference = [math]::Abs($Difference.Days)  
 }  
 if (($Difference -ge $RebootThreshold) -and ($Rebooted -eq 0)) {  
      if ((Test-Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot") -eq $true) {  
           Set-ItemProperty -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot" -Name Rebooted -Value 1 -Type DWORD -Force  
           $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot"  
           $Rebooted = $Rebooted.Rebooted  
      } else {  
           Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot" -Name Rebooted -Value 1 -Type DWORD -Force  
           $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot"  
           $Rebooted = $Rebooted.Rebooted  
      }  
 }  
 if (($Difference -lt $RebootThreshold) -and ($Rebooted -eq 1)) {  
      if ((Test-Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot") -eq $true) {  
           Set-ItemProperty -Name Rebooted -Value 0 -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot" -Type DWORD -Force  
           $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Reboot"  
           $Rebooted = $Rebooted.Rebooted  
      } else {  
           Set-ItemProperty -Name Rebooted -Value 0 -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot" -Type DWORD -Force  
           $Rebooted = Get-ItemProperty -Name Rebooted -Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Reboot"  
           $Rebooted = $Rebooted.Rebooted  
      }  
      Write-Host "System is within"$RebootThreshold" Day Reboot Threshold"  
 }  
 Write-Host "Reboot Threshold:"$RebootThreshold  
 Write-Host "Difference:"$Difference  
 Write-Host "Rebooted:"$Rebooted  
   

16 June 2015

Deploying Workshare Professional

Posted By: Mick Pletcher - 10:00 AM





Deloying Workshare Professsional is by no means an easy task for configuring the installation if you try and edit the MSI with ORCA. When configuring the MSI to customize the installation of Workshare for office and document management integration, I found the easiest way is to set specific registry keys before the install takes place. If the keys are set first, the MSI will read those keys and configure the app during the installation. Once these keys are installed, you can then run the WorkshareProfessional.msi with the switches /qb- or /qn and nothing else. Of course I installed the prerequisites first before executing the Worshare msi.

32-Bit System:
[HKEY_LOCAL_MACHINE\SOFTWARE\Workshare\Install]
"ProfInterwovenModule"=dword:00000000
"ProfCompareModule"=dword:00000001
"ProfHummingbirdModule"=dword:00000000
"OfficeExcelIntegration"=dword:00000000
"OfficeOutlookIntegration"=dword:00000000
"OfficePowerPointIntegration"=dword:00000000
"OfficeWordIntegration"=dword:00000000
"ProfNetDocumentsModule"=dword:00000001
"ProfWorldoxModule"=dword:00000000
"ProfProtectModule"=dword:00000000
"ProfReviewModule"=dword:00000000
"ProfSecureFileTransferModule"=dword:00000000
"ProfSharepointModule"=dword:00000000

64-Bit System:
[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Workshare\Install]
"ProfInterwovenModule"=dword:00000000
"ProfCompareModule"=dword:00000001
"ProfHummingbirdModule"=dword:00000000
"OfficeExcelIntegration"=dword:00000000
"OfficeOutlookIntegration"=dword:00000000
"OfficePowerPointIntegration"=dword:00000000
"OfficeWordIntegration"=dword:00000000
"ProfNetDocumentsModule"=dword:00000001
"ProfWorldoxModule"=dword:00000000
"ProfProtectModule"=dword:00000000
"ProfReviewModule"=dword:00000000
"ProfSecureFileTransferModule"=dword:00000000
"ProfSharepointModule"=dword:00000000


14 April 2015

Move PowerShell console during script execution

Posted By: Mick Pletcher - 10:32 PM














I heavily use PowerShell during the SCCM imaging process for installing each application. There are often times that I will watch the build to make sure everything is working correctly. When it calls a task sequence which calls a PowerShell script, the script will often open up in the top left of the screen and is covered up by the build status window. This function and the AutoIT script will allow for the console window to be moved while the script is executing. This does require executing the AutoIT script from the powershell script. There have been other methods shown for doing this, but from what I have seen, they are far more cumbersome.


AutoIT Script
 Sleep(500)  
 $WindowName = $CmdLine[1]  
 If WinExists($WindowName) Then  
      $aWinGetPos = WinGetPos($WindowName)  
      $Y = (@DesktopHeight / 2) - ($aWinGetPos[2] / 3)  
      $X = (@DesktopWidth / 2) - ($aWinGetPos[3] / 1)  
      WinMove($WindowName, "", $X, $Y)  
 EndIf  
   


PowerShell Function
 function MoveConsoleWindow {  
      $WindowName = $Host.ui.RawUI.WindowTitle  
      start-process -filepath "\\FileShare\MovePowerShellWindow\CenterPowerShellWindow.exe" -argumentlist $WindowName  
 }  
   

10 April 2015

Application List

Posted By: Mick Pletcher - 10:53 AM





This script will generate a list of installed applications minus those in the exclusion list text file. I created this script so the help desk could have a concise list of applications that need to be installed post-build. The script excludes all apps that are in the exclusion list, which includes everything in our build. I wrote the script so that it can be executed from SCCM on a per system basis. It could be modified to allow it to execute on remote machines.

The first thing to do is to create the ExclusionList.txt file and place it in the same directory as the script. Next is create a package in SCCM. That is all that is to it.

You can download the script from here.


 <#  
 .SYNOPSIS  
   Installed Applications  
 .DESCRIPTION  
   This will retrieve the list of installed applications from   
   add/remove programs  
 .Author  
   Mick Pletcher  
 .Date  
   09 April 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file InstalledApplications.ps1  
 #>  
   
 #Declare Global Variables  
 Set-Variable -Name Architecture -Scope Global -Force  
 Set-Variable -Name Applications -Scope Global -Force  
 Set-Variable -Name LogFile -Value "c:\Applications.csv" -Scope Global -Force  
 Set-Variable -Name RelativePath -Scope Global -Force  
   
 function Get-RelativePath {  
      $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"  
 }  
   
 function Get-Architecture {  
      $Global:Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
      $Global:Architecture = $Global:Architecture.OSArchitecture  
      #Returns 32-bit or 64-bit  
 }  
   
 function CreateLogFile {  
      #Define Local Variables  
      Set-Variable -Name Output -Scope Local -Force  
      Set-Variable -Name Temp -Scope Local -Force  
        
      if ((Test-Path $Global:LogFile) -eq $true) {  
           Remove-Item -Path $Global:LogFile -Force  
      }  
      if ((Test-Path $Global:LogFile) -eq $false) {  
           $Temp = New-Item -Path $Global:LogFile -ItemType file -Force  
      }  
      $Output = "Application Name"  
      Out-File -FilePath $Global:LogFile -InputObject $Output -Append -Force -Encoding UTF8  
                       
        
      #Cleanup Local Variables  
      Remove-Variable -Name Output -Scope Local -Force  
      Remove-Variable -Name Temp -Scope Local -Force  
 }  
   
 function GetAddRemovePrograms {  
      #Define Local Variables  
      Set-Variable -Name Applicationsx86 -Scope Local -Force  
      Set-Variable -Name Applicationsx64 -Scope Local -Force  
      Set-Variable -Name ARPx86 -Scope Local -Force  
      Set-Variable -Name ARPx64 -Scope Local -Force  
        
      $ARPx86 = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"  
      $ARPx64 = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"  
      if ($Global:Architecture -eq "32-bit") {  
           $Applicationsx86 = Get-ChildItem -Path $ARPx86 | ForEach-Object -Process {$_.GetValue("DisplayName")}  
      } else {  
           $Applicationsx86 = Get-ChildItem -Path $ARPx64 | ForEach-Object -Process {$_.GetValue("DisplayName")}  
           $Applicationsx64 = Get-ChildItem -Path $ARPx86 | ForEach-Object -Process {$_.GetValue("DisplayName")}  
      }  
      $Global:Applications = $Applicationsx86 + $Applicationsx64  
      $Global:Applications = $Global:Applications | Sort-Object  
      $Global:Applications = $Global:Applications | select -Unique  
        
      #Cleanup Local Memory  
      Remove-Variable -Name Applicationsx86 -Force  
      Remove-Variable -Name Applicationsx64 -Force  
      Remove-Variable -Name ARPx86 -Force  
      Remove-Variable -Name ARPx64 -Force  
 }  
   
 function GenerateReport {  
      #Define Local Variables  
      Set-Variable -Name Application -Scope Local -Force  
      Set-Variable -Name Exclusions -Scope Local -Force  
      Set-Variable -Name LogFile -Scope Local -Force  
      Set-Variable -Name Print -Value $true -Scope Local -Force  
        
      $Exclusions = Get-Content -Path $RelativePath"ExclusionList.txt"  
      foreach ($Application in $Global:Applications) {  
           if ($Application -ne $null) {  
                $Application = $Application.Trim()  
           }  
           if (($Application -ne "") -and ($Application -ne $null)) {  
                foreach ($Exclusion in $Exclusions) {  
                     $Exclusion = $Exclusion.Trim()  
                     if ($Application -like $Exclusion) {  
                          $Print = $false  
                     }  
                }  
                if ($Print -eq $true) {  
                     Write-Host $Application  
                     Out-File -FilePath $Global:LogFile -InputObject $Application -Append -Force -Encoding UTF8  
                }  
           }  
           $Print = $true  
      }  
   
      #Cleanup Local Variables  
      Remove-Variable -Name Application -Scope Local -Force  
      Remove-Variable -Name Exclusions -Scope Local -Force  
      Remove-Variable -Name LogFile -Scope Local -Force  
      Remove-Variable -Name Print -Scope Local -Force  
 }  
   
 cls  
 Get-RelativePath  
 Get-Architecture  
 CreateLogFile  
 GetAddRemovePrograms  
 GenerateReport  
   
 #Cleanup Global Variables  
 Remove-Variable -Name Architecture -Force  
 Remove-Variable -Name Applications -Force  
 Remove-Variable -Name LogFile -Force  
 Remove-Variable -Name RelativePath -Force  
   

08 April 2015

SCCM PowerShell SCUP Alternative

Posted By: Mick Pletcher - 11:10 AM












I have wanted to be able to update an application on the fly without having to create new application packages in SCCM. This is especially useful for applications that are updated quite frequently, such as Java Runtime Environment. This can be done by using the custom script detection method. The following script I wrote will go out and extract the product code from the MSI installer located in the source installation directory. It then looks at the HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{GUID} to make sure the application is installed. If that registry key is missing, SCCM will begin installation of the new software.

To make this work correctly, you will need to use the Uninstall-MSIbyName function included in my Deployment Module and listed below. Some installers uninstall the old version, but for best results, I always make sure I perform the uninstall before the install takes place. This will allow for the install script to uninstall the old version and then the installer can come back and install the new version. This function allows for you to input just a portion of the application name listed in Add/Remove programs. An example is Java. In my Java installer, I use the line below which uninstalls all Java version 8 packages:

Uninstall-MSIByName -ApplicationName "Java 8" -Switches "/qb- /norestart"

Next, you will need to rename the MSI installer to a generic name. I called mine JREx86.msi and JREx64.msi. When a new version comes out, I delete these and rename the new version to the same name. The powershell installation package will always look for the same named installer. Once you replace the MSI files, all that is required is to go into the SCCM console, select the deployment, and click Update Content. Once the content is updated on the distribution points and systems begin running the Application Deployment Evaluation Cycle, the package will begin installing the new version of the software.

This script only covers installations involving MSI files. I am also going to write a detection method to cover EXE files too. 

 Function Uninstall-MSIByName {  
      <#  
      .SYNOPSIS  
           Uninstall-MSIByName  
      .DESCRIPTION  
           Uninstalls an MSI application using the MSI file  
      .EXAMPLE  
           Uninstall-MSIByName -ApplicationName "Adobe Reader" -Switches "/qb- /norestart"  
      #>  
   
      Param([String]$ApplicationName, [String]$Switches)  
      $Uninstall = Get-ChildItem HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
      $Uninstall += Get-ChildItem HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall -Recurse -ea SilentlyContinue  
      $SearchName = "*"+$ApplicationName+"*"  
      $Executable = $Env:windir+"\system32\msiexec.exe"  
      Foreach ($Key in $Uninstall) {  
           $TempKey = $Key.Name -split "\\"  
           If ($TempKey[002] -eq "Microsoft") {  
                $Key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"+$Key.PSChildName  
           } else {  
                $Key = "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\"+$Key.PSChildName  
           }  
           If ((Test-Path $Key) -eq $true) {  
                $KeyName = Get-ItemProperty -Path $Key  
                If ($KeyName.DisplayName -like $SearchName) {  
                     $TempKey = $KeyName.UninstallString -split " "  
                     If ($TempKey[0] -eq "MsiExec.exe") {  
                          $Output = "Uninstall "+$KeyName.DisplayName+"....."  
                          Write-Host "Uninstall"$KeyName.DisplayName"....." -NoNewline  
                          $Parameters = "/x "+$KeyName.PSChildName+[char]32+$Switches  
                          $ErrCode = (Start-Process -FilePath $Executable -ArgumentList $Parameters -Wait -Passthru).ExitCode  
                          If (($ErrCode -eq 0) -or ($ErrCode -eq 3010) -or ($ErrCode -eq 1605)) {  
                               $Output = $Output+"Success"  
                               Write-Host "Success" -ForegroundColor Yellow  
                          } else {  
                               $Output = $Output+"Failed with error code "+$ErrCode  
                               Write-Host "Failed with error code "$ErrCode -ForegroundColor Red  
                               $Global:Errors++  
                          }  
                          Out-File -FilePath $Global:LogFile -InputObject $Output -Append -Force  
                     }  
                }  
           }  
      }  
 }  
   


 <#  
 .SYNOPSIS  
   SCCM Application Detection for Java Runtime Environment  
 .DESCRIPTION  
   This script will detect if the latest version of Java is installed by performing a query  
   on the MSI for the product key. It then searches the uninstall registry keys for a matching  
   GUID. If it does match, then the script returns a 0 back to SCCM. If it does not match,  
   then the script does not return any code back to SCCM.  
 .Author  
   Mick Pletcher  
 .Date  
   07 April 2015  
 #>  
   
 $Architecture = Get-WmiObject -Class Win32_OperatingSystem | Select-Object OSArchitecture  
 $Architecture = $Architecture.OSArchitecture  
 If ($Architecture -eq "32-bit") {  
      [string]$MSIPath = "\\FileShare\Oracle\JavaTest\JREx86.msi"  
 } else {  
      [string]$MSIPath = "\\FileShare\Oracle\JavaTest\JREx64.msi"  
 }  
 #"ProductCode","ProductVersion","ProductName"  
 [string]$Property = "ProductCode"  
 $WindowsInstaller = New-Object -ComObject WindowsInstaller.Installer  
 $MSIDatabase = $WindowsInstaller.GetType().InvokeMember("OpenDatabase","InvokeMethod",$Null,$WindowsInstaller,@($MSIPath,0))  
 $Query = "SELECT Value FROM Property WHERE Property = '$($Property)'"  
 $View = $MSIDatabase.GetType().InvokeMember("OpenView","InvokeMethod",$null,$MSIDatabase,($Query))  
 $View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)  
 $Record = $View.GetType().InvokeMember("Fetch","InvokeMethod",$null,$View,$null)  
 $Value = $Record.GetType().InvokeMember("StringData","GetProperty",$null,$Record,1)  
 If ($Architecture -eq "32-bit") {  
      $RegPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"+$Value  
 } else {  
      $RegPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"+$Value  
 }  
 If ((Test-Path $RegPath) -eq $true) {  
      Return 0  
 } else {}  
   

06 April 2015

Get Default Printer

Posted By: Mick Pletcher - 12:55 PM





This script will find the default printer of a logged on user and write it to a text file named DefaultPrinter.txt in the %APPDATA% folder. I have this script run as a package in SCCM on a reoccurring weekly basis. This keeps the file up to date in the event a user needs a new machine or a new profile.

You can download the file from here: GetDefaultPrinter.ps1



 <#  
 .SYNOPSIS  
   Get Default Printer  
 .DESCRIPTION  
   Gets the default printer and writes the printer to a text file in the %APPDATA% folder. If  
   this is executed through SCCM, it must be run as the user.  
 .Author  
   Mick Pletcher  
 .Date  
   06 April 2015  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file GetDefaultPrinter.ps1  
 #>  
   
 #Declare Global Variables  
 Set-Variable -Name DefaultPrinter -Scope Global -Force  
   
 cls  
 If ((Test-Path $env:APPDATA"\DefaultPrinter.txt") -eq $true) {  
      Remove-Item -Path $env:APPDATA"\DefaultPrinter.txt" -Force  
 }  
 $DefaultPrinter = Get-WmiObject -Class win32_printer -ComputerName "localhost" -Filter "Default='true'" | Select-Object ShareName  
 Write-Host "Default Printer: " -NoNewline  
 If ($DefaultPrinter.ShareName -ne $null) {  
      $DefaultPrinter.ShareName | Out-File -FilePath $env:APPDATA"\DefaultPrinter.txt" -Force -Encoding "ASCII"  
      Write-Host $DefaultPrinter.ShareName  
 } else {  
      $DefaultPrinter = "No Default Printer"  
      $DefaultPrinter | Out-File -FilePath $env:APPDATA"\DefaultPrinter.txt" -Force -Encoding "ASCII"  
      Write-Host $DefaultPrinter  
 }  
   
 #Cleanup Global Variables  
 Remove-Variable -Name DefaultPrinter -Scope Global -Force  
   

27 March 2015

OS Detection

Posted By: Mick Pletcher - 11:37 AM





Recently, we added the Windows 8.1 operating system to the company domain. I ran into the problem of deploying Adobe Flash Player to these systems because the ActiveX does not get installed. The issue came up in SCCM where I needed a detection method for the OS using file, registry, or product ID. I needed a way to detect the plugin and windows 8.1 to be able to validate a successful deployment. This gave me the idea to write a powershell script to place a file in the windows directory that is named for the operating system that is running. It makes for easy OS detection in SCCM for future deployments.



 <#  
 .SYNOPSIS  
   Operating System  
 .DESCRIPTION  
   This script will detect what operating system is installed  
   and write a file named for that OS to the windows directory.  
 .EXAMPLE  
   powershell.exe -executionpolicy bypass -file OperatingSytem.ps1  
 #>  
   
 cls  
 $OS = Get-WmiObject -Class Win32_OperatingSystem -ComputerName .  
 Switch ($OS.Version) {  
      5.0.2195 {New-Item -Name "Windows 2000" -Path $env:windir -ItemType File}  
      5.1.2600 {New-Item -Name "Windows XP" -Path $env:windir -ItemType File}  
      5.2.3790 {New-Item -Name "Windows XP 64-Bit" -Path $env:windir -ItemType File}  
      6.0.6000 {New-Item -Name "Windows Vista" -Path $env:windir -ItemType File}  
      6.0.6001 {New-Item -Name "Windows Vista SP1" -Path $env:windir -ItemType File}  
      6.0.6002 {New-Item -Name "Windows Vista SP2" -Path $env:windir -ItemType File}  
      6.1.7600 {New-Item -Name "Windows 7" -Path $env:windir -ItemType File}  
      6.1.7601 {New-Item -Name "Windows 7 SP1" -Path $env:windir -ItemType File}  
      6.2.9200 {New-Item -Name "Windows 8" -Path $env:windir -ItemType File}  
      6.3.9600 {New-Item -Name "Windows 8.1" -Path $env:windir -ItemType File}  
 }  
   

27 February 2015

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

Posted By: Mick Pletcher - 1:19 PM
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

Posted By: Mick Pletcher - 10:23 PM





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

Posted By: Mick Pletcher - 10:37 PM














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

Posted By: Mick Pletcher - 1:19 PM














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

Posted By: Mick Pletcher - 12:07 PM





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  

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