14 April 2015

Move PowerShell console during script execution

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

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

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

The new and updated Default Printer report is located here.

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