Code Release
FileSplitter is a PowerShell script designed to offer a method of splitting files without having to use any 3rd party software.
Code releases, and sys admin info
A post which containing or about PowerShell
Code Release
FileSplitter is a PowerShell script designed to offer a method of splitting files without having to use any 3rd party software.
Code Release
This script is intended to assist in SCCM deployments of optional printers. The intended effect is to allow a user to open Software Center or the SCCM Application Catalog and click an option such as “Map me to XXX Printer”, and have the printer, port, and driver all install automatically.
DriverName <Object> The name of the printer driver as defined in the INF file.
DriverInf <Object> Full path to the Driver INF file
DriverPath <Object> Path to the folder containing the driver files. (Default is the INF directory)
PrinterIP <Object> IP of the printer to map
PrinterPort <Object> Printer’s connection port. Default is 9100
PrinterPortName <Object> Name of the printer port. Defaults to “IP_<IP>”
PrinterCaption <Object> Name of the printer
Remove [<SwitchParameter>] Removes the specified printer instead of installing it
DriverOnly [<SwitchParameter>] Only installs the printer driver. Does not attempt to create the port or printer
AllSteps [<SwitchParameter>] Performs Driver install followed by the port and printer mappings. (Default without this and driver only is to only map a printer)
WriteLog [<SwitchParameter>] Writes Debug data to a log file. (Default is true)
RemoveAll [<SwitchParameter>] Removes all mapped printers. (You may need to run this once as the user, and once as an admin for full effect)
AdditionalRemovalExclusions <Object> When used with RemoveAll, prevents the removal of listed printers. By default, Send to One Note, and FAX are excluded.
To add a printer:
&"" -PrinterCaption "My Printer" -DriverName "Xerox 7845 PS v4" -PrinterIP "192.168.1.2"
To add a driver:
&"" -DriverName "Xerox 7845 PS v4" -DriverInf "\\Drivers\Xerox\7845\Driver.inf" -DriverOnly
To remove a printer and also disable logging
&"" Install-Printer.ps1 -PrinterCaption "My Printer" -Remove -WriteLog:$false
To install a driver and then map a printer (Note that, in general, only administrators have the permissions to successfully use the script this way)
&"" -PrinterCaption "My Printer" -DriverName "Xerox 7845 PS v4" -DriverInf "\\Drivers\Xerox\7845\Driver.inf" -PrinterIP "192.168.1.2" -AllSteps
To remove all printers, or a selection of printers
&"" -RemoveAll -AdditionalRemovalExclusions @("My Printer", "Send by E-mail printer")
To install a printer from SCCM, do the following.
&"Install-Printer.ps1" -DriverOnly -DriverName '' -DriverINF ''
<#
.SYNOPSIS
Provides helpfull script to add printers to a machine
.DESCRIPTION
Provides a method to add a printer, driver, and port to a machine
.PARAMETER DriverName
The name of the printer driver as defined in the INF file.
.PARAMETER DriverInf
Full path to the Driver INF file
.PARAMETER DriverPath
Path to the folder containing the driver files. (Default is the INF directory)
.PARAMETER PrinterIP
IP of the printer to map
.PARAMETER PrinterPort
Printer's connection port. Default is 9100
.PARAMETER PrinterPortName
Name of the printer port. Defaults to "IP_". If you need to map the printer to a default local port, use the name of the port instead. Such as "FILE:".
.PARAMETER PrinterCaption
Name of the printer
.PARAMETER Remove
Removes the specified printer instead of installing it
.PARAMETER DriverOnly
Only installs the printer driver. Does not attempt to create the port or printer
.PARAMETER AllSteps
Performs Driver install followed by the port and printer mappings. (Default without this and driver only is to only map a printer)
.PARAMETER WriteLog
Writes Debug data to a log file. (Default is true)
.PARAMETER RemoveAll
Removes all mapped printers. (You may need to run this once as the user, and once as an admin for full effect)
.PARAMETER LocalPrinter
Specified that the printer is local to the machine. This is needed if mapping a non-network device. (Such as the XPS printer)
.PARAMTER PrinterOnly
Only maps a printer, does not add a port, nor install a driver.
.PARAMETER AdditionalRemovalExclusions
When used with RemoveAll, prevents the removal of listed printers. By default, Send to One Note, and FAX are excluded.
.OUTPUTS
Log file located at C:\Temp\.log
.EXAMPLE
Install-Printer.ps1 -PrinterCaption "My Printer" -DriverName "Xerox 7845 PS v4" -PrinterIP "192.168.1.2"
.EXAMPLE
Install-Printer.ps1 -DriverName "Xerox 7845 PS v4" -DriverInf "\\Drivers\Xerox\7845\Driver.inf" -DriverOnly
.EXAMPLE
Install-Printer.ps1 -PrinterCaption "My Printer" -Remove -WriteLog:$false
.EXAMPLE
Install-Printer.ps1 -PrinterCaption "My Printer" -DriverName "Xerox 7845 PS v4" -DriverInf "\\Drivers\Xerox\7845\Driver.inf" -PrinterIP "192.168.1.2" -AllSteps
.EXAMPLE
Install-Printer.ps1 -RemoveAll -AdditionalRemovalExclusions @("My Printer", "Send by E-mail printer")
.EXAMPLE
Install-Printer.ps1 -PrinterCaption 'Microsoft XPS Document Writer' -PrinterPortName 'FILE:' -DriverName 'Microsoft XPS Document Writer v4' -PrinterOnly -LocalPrinter
.NOTES
Version: 1.3
Author: Matthew Thompson
Creation Date: 2015-12-16
Purpose/Change: PrinterOnly, and LocalPrinter Features, Printer Removal by IP, Bug fix in event no printers exist and script is run to install one.
This script was heavily influenced by Kris Powell. (http://www.adminarsenal.com/admin-arsenal-blog/how-to-add-printers-with-powershell)
#>
[CmdletBinding()]
Param
(
[string]$DriverName,
[string]$DriverInf,
[string]$DriverPath={if ($DriverINF -ne $null){(Split-Path $DriverInf)}},
[string]$PrinterIP,
[int]$PrinterPort=9100,
[string]$PrinterPortName=("IP_"+$PrinterIP),
[string]$PrinterCaption,
[Switch]$Remove,
[switch]$DriverOnly,
[switch]$AllSteps,
[switch]$WriteLog=$true,
[switch]$RemoveAll,
[string[]]$AdditionalRemovalExclusions=@(),
[switch]$LocalPrinter,
[switch]$PrinterOnly
)
#Setup the exclusions for RemoveAll
$Exclusions = @("Fax", "Send To OneNote 2013", "Send To OneNote 2010", "Microsoft XPS Document Writer", "Microsoft XPS Document Writer v4")
$Exclusions+=$AdditionalRemovalExclusions
$Log=""
#Log File Info
$ScriptName = Split-Path -Leaf $PSCommandPath
$LogFile = Join-Path -Path "C:\Windows\Temp" -ChildPath ($ScriptName+".log")
#Do a self-referential lookup to get the script version. As long as the .NOTES section of the file header is up to date and the script is trusted, this should work.
(Get-Help $PSCommandPath -Full -ErrorAction SilentlyContinue).alertset.alert.Text -match "^\s*Version\s*:\s*[\w\d\.\,]*" | Out-Null
if ($matches-ne$null-and$matches.Count-ge1)
{
$Version = $matches[0] -replace "^\s*Version\s*:\s*",""
}
#Write log data
$Log+="Version: "+$Version+"`r`n"
#Write Parameters to log
$PSBoundParameters.Keys | ForEach-Object -Process {$Log+=("`""+$_+"`" = `""+$PSBoundParameters[$_]+"`"`r`n")}
#Creates a printer object for the user.
function Create-Printer
{
Param
(
$PrinterCaption,
$PrinterPortName,
$DriverName,
[Switch]$Local=$false
)
try
{
$Printers = @()
$Printers += Get-WmiObject -Class "Win32_Printer"
[string]$Log="Installed Printers ("+$Printers.Length.ToString()+") `r`n"
$Printers | ForEach-Object -Process {$Log+=$_.Name+"`r`n"}
if ($Printers.Length -eq 0 -or ($Printers.Name).Contains($PrinterCaption) -eq $false)
{
$Log+="Attempting to create printer`r`n"
$Instance = Set-WmiInstance -Class "Win32_Printer" -Arguments @{Caption = $PrinterCaption;DriverName = $DriverName;PortName = $PrinterPortName;DeviceID = $PrinterCaption;Network = (-not $Local)} -ErrorAction Stop
if ($Instance -ne $null)
{
$Log+= "Printer Created`r`n"+$Instance.ToString()+"`r`n"
}
else
{
$Log+="Install attempted, but printer null`r`n"
}
}
$Log+= "Printer Exists: "+(($Printers.Name).Contains($PrinterCaption)).ToString()+"`r`n"
}
catch
{
$Log+=$_.ToString()+"`r`n"
}
return $Log
}
#Installs a driver from an INF file
function Install-Driver
{
Param
(
$DriverName,
$DriverPath,
$DriverInf
)
$Log=""
try
{
$Drivers = @()
$Drivers += Get-WmiObject -Class "Win32_PrinterDriver"
if ($Drivers.Length -eq 0 -or -not ($Drivers | Where-Object -FilterScript {$_.Name -like ($DriverName+"*")}))
{
$Instance = ([wmiclass]"Win32_PrinterDriver").CreateInstance()
$Instance.Name = $DriverName
$Instance.DriverPath = $DriverPath
$Instance.InfName = $DriverInf
Invoke-WmiMethod -Class "Win32_PrinterDriver" -Name "AddPrinterDriver" -ArgumentList @($Instance) -Impersonation Impersonate -EnableAllPrivileges | Out-Null
$Log+= $Instance.ToString()+"`r`n"
}
else
{
$Log+= ("Driver Exists: "+(($Drivers | Where-Object -FilterScript {$_.Name -like ($DriverName+"*")})).ToString())+"`r`n"
}
}
catch
{
$Log+=$_.ToString()+"`r`n"
}
return $Log
}
#Creates a printer port required to map a printer
function Create-PrinterPort
{
Param
(
$PrinterIP,
$PrinterPort,
$PrinterPortName
)
$Log=""
try
{
$PrinterPorts = @()
$PrinterPorts += Get-WmiObject -Class "Win32_TCPIPPrinterPort"
if ($PrinterPorts.Length -eq 0 -or -not ($PrinterPorts.Name).Contains($PrinterPortName))
{
$Instance = Set-WmiInstance -Class "Win32_TCPIPPrinterPort" -Arguments @{Name = $PrinterPortName;HostAddress = $PrinterIP;PortNumber = $PrinterPort;SNMPEnabled = $false;Protocol = 1}
$Log+= $Instance.ToString()+"`r`n"
}
else
{
$Log+= ("Printer Port Exists: "+(($PrinterPorts.Name).Contains($PrinterPortName)).ToString())+"`r`n"
}
}
catch
{
$Log+=$_.ToString()+"`r`n"
}
return $Log
}
function Get-PrinterPort
{
Param($IP)
$Log=""
$ToReturn = @()
try
{
$PrinterPorts = @()
$PrinterPorts += Get-WmiObject -Class "Win32_TCPIPPrinterPort"
if ($PrinterPorts.Length -eq 0 -or -not ($PrinterPorts.HostAddress).Contains($IP))
{
#$Instance = Set-WmiInstance -Class "Win32_TCPIPPrinterPort" -Arguments @{Name = $PrinterPortName;HostAddress = $PrinterIP;PortNumber = $PrinterPort;SNMPEnabled = $false;Protocol = 1}
$Log+= "No Printer Port exists matching $IP"
}
else
{
$ToReturn = $PrinterPorts | Where-Object -FilterScript {$_.HostAddress -eq $IP}
$Log+= ("Printer Port Exists: "+(($PrinterPorts.Name).Contains($PrinterPortName)).ToString())+"`r`n"
}
}
catch
{
$Log+=$_.ToString()+"`r`n"
}
return $ToReturn
}
#Deletes an already mapped printer
function Delete-Printer
{
Param($PrinterName, $IP)
$Log=""
try
{
$Printers = @()
$Printers += Get-WmiObject -Class "Win32_Printer"
if ($PrinterName -ne $null)
{
$Printers = $Printers | Where-Object -FilterScript {$_.Name -eq $PrinterName}
}
elseif ($IP -ne $null)
{
$Ports = Get-PrinterPort -IP $IP
$NewPrinterList = @()
foreach ($Port in $Ports)
{
$NewPrinterList += $Printers | Where-Object -FilterScript {$_.PortName -eq $Port.Name}
}
$Printers = $NewPrinterList
}
else
{
$Log+="No parameters specified to limit removal. No printers removed"
return $Log
}
foreach($Printer in $Printers)
{
$Printer.Delete() | Out-Null
$Log+= $Printer.Name+" deleted`r`n"
}
}
catch
{
$Log+=$_.ToString()+"`r`n"
}
return $Log
}
#Script Entry
if ($Remove)
{
Write-Host "Removing $PrinterCaption"
#Remove a printer selected
$Log+=Delete-Printer -PrinterName $PrinterCaption -IP $PrinterIP
}
elseif($RemoveAll)
{
#Remove most printers
$Printers = @()
$Printers += Get-WmiObject -Class "Win32_Printer" | Where-Object -FilterScript {$Exclusions.Contains($_.Name) -eq $false}
if ($Printers.Length -gt 0)
{
Write-Host ("Attempting to remove "+$Printers.Length+" printers. You may need to run this script once as admin, and once as the user for all printers to be removed.")
foreach($Printer in $Printers)
{
$Log+=(Delete-Printer -PrinterName $Printer.Name)+"`r`n"
}
}
else
{
Write-Host "No printers require removal."
}
}
else
{
#Install a printer
if ($DriverOnly -or $AllSteps)
{
#Install the printer Driver
Write-Host "Installing $DriverName"
$Log+=(Install-Driver -DriverName $DriverName -DriverPath $DriverPath -DriverInf $DriverInf)+"`r`n"
}
if (-not $DriverOnly -and -not $PrinterOnly)
{
#Output for user visibility
Write-Host "Mapping printer ($PrinterCaption) from IP $PrinterIP"
#Add the printer port
$Log+=(Create-PrinterPort -PrinterIP $PrinterIP -PrinterPort $PrinterPort -PrinterPortName $PrinterPortName)+"`r`n"
#Finally, create the printer
$Log+=(Create-Printer -PrinterCaption $PrinterCaption -PrinterPortName $PrinterPortName -DriverName $DriverName -Local $LocalPrinter)+"`r`n"
}
if ($PrinterOnly)
{
#Output for user visibility
Write-Host "Mapping printer ($PrinterCaption) from port $PrinterPortName"
$Log+=(Create-Printer -PrinterCaption $PrinterCaption -PrinterPortName $PrinterPortName -DriverName $DriverName -Local $LocalPrinter)+"`r`n"
}
}
#Write the log to a file
if ($WriteLog)
{
$Log | Out-file $LogFile
Write-Verbose $Log
}
Edit the “lt;DriverName>” Portion of the script below with the name of your driver. Then add the script into SCCM as your script-based detection method for the INF driver installation.
#Note that this script is not perfect, you may need to change the detection method if you have multiple similiarly named drivers.
$DriverName = "" #Replace this with your driver name
$Drivers = Get-WmiObject -Class "Win32_PrinterDriver"
if (($Drivers | Where-Object -FilterScript {$_.Name -like ($DriverName+"*")}))
{
Write-Host "Driver already installed"
}
exit 0
Edit the “<PrinterName>” portion of the script below with your own printer name. Then add this to your printer deployment as the script-based detection method.
$PrinterCaption = "" #Change this to your printer name
$Printers = Get-WmiObject -Class "Win32_Printer"
if (($Printers.Name).Contains($PrinterCaption))
{
Write-Host "Printer already installed"
}
exit 0
This must be placed in the same directory as Install-Printer.ps1 This will present a user with a list of printers to exclude. The user can then select a printer by inputting it’s number. If they do, it will be excluded. Once finished, the script will remove all other printers.
<# .SYNOPSIS Interactively assists a user in removing printers .DESCRIPTION Interactively assists a user in removing printers .NOTES Version: 1.0 Author: Matthew Thompson Creation Date: 2017-01-08 Purpose/Change: Updated for Blog #>
#Printers to exclude by default
$Exclusions = @("Fax", "Send To OneNote 2013", "Send To OneNote 2010")
#Grab a list of printers
$Printers = Get-WmiObject -Class "Win32_Printer" | Where-Object -FilterScript {$Exclusions.Contains($_.Name) -eq $false}
#Stores additional exclusions by user
$AdditionalExclusions=@()
#Infinite loop (Breaks out with user interaction)
while($true)
{
Write-Host "Printers to remove:"
#Print all non excluded printers for user to browse
$NonExcluded=@()
$NonExcluded += $Printers | Where-Object -FilterScript {$AdditionalExclusions.Contains($_.Name) -eq $false}
for($I=0;$I -lt $NonExcluded.Length;$I++)
{
Write-Host ($I.ToString()+") "+$NonExcluded[$I].Name)
}
#Ask user to exlude another printer or continue
$UInput = Read-Host -Prompt "Please select a printer to exclude or type `"f`" to finish"
if ($UInput.ToLower() -eq "f")
{
#On continue, break out of infinite loop
break;
}
#Otherwise matching selected index to printer, and add.
$UIndex=$null
if ([int]::TryParse($UInput, [ref]$UIndex) -and $UIndex -ge 0 -and $UIndex -lt $NonExcluded.Length)
{
$AdditionalExclusions+=$NonExcluded[$UIndex].Name
}
else
{
#In case someone can't type a number
Write-Host "Input could not be parsed, please try again"
}
}
#Call the parent script with the selected exclusions
Write-Host "Removing printers"
&".\Install-Printer.ps1" -RemoveAll -AdditionalRemovalExclusions $AdditionalExclusions -WriteLog
Code Release
MTPing (or Multi-Threaded Ping) is a powershell script designed to quickly ping a large list of machines.
This parameter should be a string that points to the location of a list of machines. The list should be formatted so that there is one machine per line.
Example:
192.168.1.1 127.0.0.1 www.google.com microsoft.com ::1
LogLocation should be a string path representing where the log file should be saved. The log file will be written in a simple tabular format. The first column contains the machine-name or ip as retrieved from the machine list. The second column contains the ping result. “True” meaning connection successful.
Example:
192.168.1.1 False 127.0.0.1 True www.google.com True microsoft.com True ::1 True
Inside of a powershell console enter the following:
&"" -MachineList "" -LogLocation ""
Example:
&"C:\Scripts\MTPing.ps1" -MachineList "C:\Scripts\Machines.txt" -LogLocation "C:\Logs\PingResults.txt"
Param
(
[Parameter(Mandatory=$True)]
$MachineList,
[Parameter(Mandatory=$True)]
$LogLocation
)
$ToProcess = @()
$StreamReader = [System.IO.StreamReader] $MachineList
while (-not $StreamReader.EndOfStream)
{
$ToProcess+=$StreamReader.ReadLine();
}
$StreamReader.Close();
$ScriptBlock =
{
param
(
[PSObject]$InputObject
)
return @((Test-Connection $InputObject -quiet), $InputObject)
}
$Stream = [System.IO.StreamWriter] $LogLocation
$Pool = [RunspaceFactory]::CreateRunspacePool(1, 10)
$Pool.ApartmentState = "STA"
$Pool.Open()
$Pipes = @()
foreach($Object in $ToProcess)
{
$PipeLine = [System.Management.Automation.PowerShell]::create()
$PipeLine.RunspacePool = $Pool
$Mute=$PipeLine.AddScript($ScriptBlock).AddArgument($Object)
$Pipes+= @{
Pipe = $PipeLine
AsyncHandle = $PipeLine.BeginInvoke()
}
}
$Complete=$false;
$Completed=0;
while(-not $Complete)
{
$Found = $false;
for ($Index=0; $Index -lt $Pipes.Count; $Index++)
{
if ($Pipes[$Index] -ne $null)
{
$Found=$true;
if ($Pipes[$Index].AsyncHandle.IsCompleted)
{
$Result = $Pipes[$Index].Pipe.EndInvoke($Pipes[$Index].AsyncHandle)
$Pipes[$Index].Pipe.Dispose()
if ($Result[0])
{
Write-Host $Result[1] -ForegroundColor Green -NoNewline
Write-Host "`t"($Completed/$Pipes.Count*100)"%"
}
else
{
Write-Host $Result[1] -ForegroundColor Red -NoNewline
Write-Host "`t"($Completed/$Pipes.Count*100)"%"
}
$Stream.WriteLine($Result[1]+"`t"+$Result[0].ToString());
$Pipes[$Index] = $null;
$Completed++;
}
}
}
if (-not $Found)
{
$Complete = $true;
}
}
$Stream.Close();
$Pool.Close();
Code Release
HashCompare compares a file with a known MD5 hash and returns the output in an SCCM compatible manner. To understand more about why this script exits as it does, you should read “To use a custom script to determine the presence of a deployment type” (Under step 4) on technet.
The script is fairly simple. You only need to configure the top 2 lines. First set $FilePath to the location of the pre-existing file. Then set $ExpectedHash to the MD5 hash of the deployed file. You can get the MD5 hash from powershell using Get-Hash in Powershell 4.0 or, if you are not up to 4.0, by using this Get-Hash.
Inside of a powershell console enter the following:
&""
Example:
&"C:\Scripts\HashDetect.ps1"
After configuration, import the script as a new Detection Method for your deployment.
$FilePath = "C:\ProgramData\MyApp\MySettings.xml"
$ExpectedHash = "00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00"
if (-not (Test-Path $FilePath))
{
#File path not found. Not installed.
exit 0
}
try
{
$FileStream = New-Object IO.FileStream $FilePath, "Open"
$MD5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$Hash = [System.BitConverter]::ToString($MD5.ComputeHash($FileStream))
}
catch
{
Write-Error $_.ToString()
exit 1
}
#Compare the hash with a known hash
if ($Hash -eq $ExpectedHash)
{
Write-Output "Hash match. File already installed."
exit 0
}
else
{
#Hash MisMatch. Not installed
exit 0
}
Quick code release
Get-Hash returns the MD5 or SHA256 hash of a file. (Note: Powershell 4.0 has a built-in command for this.)
The path to the file you wish to hash.
Inside of a powershell console enter the following:
"" -FilePath ""
Example:
&"C:\Scripts\Get-Hash.ps1" -FilePath "C:\FileToHash.xml"
Param($FilePath)
if (-not (Test-Path $FilePath))
{
#File path not found. Not installed.
Write-Error "File does not exist"
}else
{
try
{
$FileStream = New-Object IO.FileStream $FilePath, "Open"
$MD5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
$Hash = [System.BitConverter]::ToString($MD5.ComputeHash($FileStream))
Write-Output $Hash
}
catch
{
Write-Error $_.ToString()
}
}
Param($FilePath)
if (-not (Test-Path $FilePath))
{
#File path not found. Not installed.
Write-Error "File does not exist"
}else
{
try
{
$FileStream = New-Object IO.FileStream $FilePath, "Open"
$SHA2 = new-object -TypeName System.Security.Cryptography.SHA256CryptoServiceProvider
$Hash = [System.BitConverter]::ToString($SHA2.ComputeHash($FileStream))
Write-Output $Hash
}
catch
{
Write-Error $_.ToString()
}
}
Release Announcement
BGPSInfo is my attempt to replace BGInfo with something customizable. Powershell was chosen to accomplish this. The script has only had limited testing so far. Use at your own risk.
You may download the PowerShell script: GitHub
This post was migrated from my older blog.
I have been trying to get Java to install smoothly from SCCM for quite a while now. We need to deploy Java in a way that ensures that old versions of Java are removed, and both 32-bit and 64-bit versions of Java are installed. Here is a list of problems a ran into, as well as fixes for each.
Uninstalling Java improves security as the Java installer frequently fails to remove previous versions. So, how do we uninstall Java remotely through SCCM? The most obvious approach is to remove Java by the GUID with MSIEXEC. Example:
MsiExec.exe /X{26A24AE4-039D-4CA4-87B4-2F06417067FF} /qn /norestart
This works well, but you have to know the GUIDs beforehand. If you search for the Java GUIDs you will likely find forums with comments stating to just replace parts of the GUID with the software version and bit version. We had a script that, mostly, worked based on this concept.
Here is the general suggestion you will find when you search for the Java GUIDs. The first bolded part of the following GUID is the bit version of Java, 32 or 64. The second bolded part relates to the specific version of Java. In this case, 7 update 55.
{26A24AE4-039D-4CA4-87B4-2F83217055FF}
This does not work all the time however.
Here’s an example:
The bolded parts changed outside the expected pattern.
That’s just one example, it seems as though subtle changes like that are littered through the Java releases. Most scripts that remove multiple versions of Java don’t seem to account for this. Our in-house script didn’t.
I noticed that the parts of the GUID that frequently change are only on the last segment of the GUID. The front segments of the GUID rarely change. I built a powershell script to remove Java on this concept.
Click here to go to the wiki page on the JavaRemoval script.
The removal works well by itself, but had issues when used in an SCCM package. It only removed 32-bit versions of Java. This leads to multiple 64-bit versions of Java. Thanks to the logging lines I added to the script I found out that it was only receiving the 32-bit keys, even from the 64-bit registry path. Turns out, SCCM executes the script with the 32-bit Powershell. This was a quick fix thanks to this technet post. I took jfvanroy’s answer and entered in the Powershell script name. It is working well so far. You can find a copy of it below.
If "%PROCESSOR_ARCHITEW6432%"=="" GOTO Native
%windir%\Sysnative\windowsPowershell\V1.0\PowerShell.exe -NoProfile -ExecutionPolicy Bypass -file %~dp0JavaRemoval.ps1
GOTO END
:Native
PowerShell.exe -NoProfile -ExecutionPolicy Bypass -file %~dp0JavaRemoval.ps1
:END
The next problem I ran into with uninstalling/installing Java is a reboot requirement. Our original batch file uninstalled and installed Java in one run. This was hit or miss for 2 reasons. The first script that we made did not have a /norestart switch to the uninstall command. When a restart was required, the machine rebooted. The problem with that is it cancels the rest of the script which prevents Java from being installed. SCCM does not resume scripts that restart in the middle of their execution. As for the second reason; when we added the /norestart switch the install fails due to a pending restart. One of the main reasons a reboot was required is because IE or Java was still in use by a user at the deployment deadline.
We originally remedied these issues by using a task-sequence with a required reboot in-between an uninstall and install step. We added a few taskkill
commands to our script to help with the program in-use issues. Not exactly elegant, but it worked… mostly. This was before we figured out the GUID issues. We are now using a single application package for uninstall and 2 application packages for install. This is done to bridge any gaps created by the install. For example, if you uninstall Java, then install the latest version, this causes a gap in the Java coverage for the user. To fix this, deploy two packages, x86 and x64, of Java. Then about a week later, deploy an uninstall package the removes all versions except for the version you want to standardize your users on.
When you use the executable installer to install Java, it extracts an MSI and CAB file to the AppData folder. It then runs a MSIEXEC /i
command to install the extracted MSI file. For home users this usually works, as it uses the home user’s account. SCCM, however, uses the system account. The system account is a little odd in that, it has two profiles. A 32-bit and a 64-bit profile. MSIEXEC is a 64-bit utility in 64-bit versions of windows, but the Java installer runs as 32-bit. The files that the installer extracts ends up in the 32-bit system profile. When MSIEXEC goes to install the MSI, it looks in the 64-bit profile and fails to find it.
This is a known bug and has been for a while. Shavlik submitted a bug report to Java back in 2010 on the issue, however it was recently resolved with the resolution “Won’t Fix”.
A workaround to this, is to extract the MSI, and CAB file for older versions, and run those directly. It works but it is an unsupported practice according to Oracle. Update: You were once able to find instructions on how to do that at Oracle’s website. This page now advertises their enterprise “My Oracle Support” option. As such, I will include the instructions below.
I briefly attempted to deploy Java through SCUP. I sent a test deployment out and it half worked. Some received the update, some didn’t. Mostly it was just a mess. At the time I was not fully sure why, but it is likely that it was due to the same issue I had deploying Java as an application package. It extracted to the wrong system profile
Before entirely giving up on the idea, I decided to see if I could send it out using the extracted MSI files. It was not even plausible as a concept as Java extracts to 2 files, a MSI and a CAB. Both files are required for the install to succeed. SCUP does not support multi-file installers unfortunately.
Update 2016-01-07: The 2 file issue appears to have stopped. Extracting Java now only seems to produce a single MSI file. I have not attempted a SCUP deployment however. If it does work, the next problem will still be an issue.
Java recently, starting with an update of 8, started to prompt during install to remove previous version of Java. This does works well for home users, but for enterprise environments, this does not work in silent mode. There are currently no switches you can use to get the MSI to remove old versions.
Another recent development with Java, is that it now appears to require a config file in order to install in silent mode correctly. The /q switch for msiexec is no longer enough.
The config file needs to be saved to “C:\ProgramData\Oracle\Java\java.settings.cfg”. The file is a plain text file with install commands on every line. The line we need to ensure a functional silent install is “INSTALL_SILENT=1”. See below for an example config file.
AUTO_UPDATE=0 EULA=0 INSTALL_SILENT=1 SPONSORS=0 WEB_ANALYTICS=0
There is a sub issue related to this. If you leave the config file in place after the install occurs, then any administrator that then needs to manually update or remove Java in the future will not see their installer/uninstaller run. More than likely, they will troubleshoot their “failing” installer until they finally realize that the installer actually is running, but is forced into silent mode by the left over file.
I have a 3 Deployment solution that appears to work solidly in SCCM. Instructions on how to create the solution is located at Java_Install_Package
In general though, the package can be built as such:
The end effect of all this is, when the install program is deployed it will install Java. The uninstall package which should be deployed a week later will uninstall un-needed versions of Java.
This post was migrated from my older blog.
There have been a few times where I found myself starting at an error prompt stating Error: 0x000006C1 or some other unhelpful hex number. Then upon searching for the error code, only finding results for a specific instance of the error for an unrelated application with generic fixes that still don’t hint at the root issue. I found that you can translate some of these error codes using .Net and by extension PowerShell. If you find yourself in this situation, try the following command:
new-object System.ComponentModel.Win32Exception @($ErrorCode);
Where “$ErrorCode” is your error. Example:
new-object System.ComponentModel.Win32Exception @(0x80190193); #Can use integers or hex numbers
I also generated a list of the exceptions here: Win32Exceptions List If the list does not contain your error code, then it is likely just “Unknown error”… not very helpful.
Alternatively, if you utilize SCCM and have the CMTrace tool, you can preform error code lookups directly from the CMTrace application.
This post was migrated from my older blog.
I wanted to sign a PowerShell script to make it easier and more secure to use in our environment. PowerShell signing was not in great use in the environment. To make signing the scripts easier I created a simple script to help with the signing process. There was an issue however. We have generic, fully trusted, all purposes certificates. Despite being fully trusted and all purpose, they did not show up as valid certificates in PowerShell. I tried manually running:
Get-ChildItem cert:\CurrentUser\My -codesigning
It returned nothing. The certificates should work, but they weren’t showing up. I checked the certificate inside of the Certificate Store. Inside the certificate’s properties on the “General” tab, there is a “Certificate purposes” group box. Inside of this “Enable all purposes for this certificate” was selected.
Since that was not working, I selected “Enable only for the following purposes”.
By default, all purposes were already checked. I clicked “Ok” and ran the Get-ChildItem command again and the certificate was finally listed. I ran through the signing process successfully. The signature on the PowerShell file was trusted and valid.