Create a Custom Installer Using Powershell

We run a program called QATRAX which doesn’t come as a MSI file for installing it.  The program has many sins: one of them being that it can only be installed to C:\program files\traxstar (it’s a x86 program) and it has to have write access to this folder.  Attempts to use a re-packager to convert this to a MSI file have failed, because all repackagers detect it should really go in C:\program files (x86), but of course that won’t work.

The program was made in the 1990s, so figuring out what files it copies to to system is quite easy.  You can use a tracing utility such as Process Monitor to watch for install changes.

# copy qatrax support files
copy-item *.dll C:\windows\syswow64 -Force

We could add registry entries using Powershell code line-by-line, but doing a registry export to a REG file and then import it is far easier in my opinion.

# import qatrax registry settings
regedit /s qatrax.reg

One thing to note is that we need to create an uninstall string manually so it shows up in Programs and Features as being installed.  This will also report back to WMI that the program is really installed to help with software inventory.  You should be able to get this from a system with the software already installed.

[HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\QATrax]
@=""
"DisplayName"="QATrax"
"UninstallString"="C:\\Program Files\\TraxStar\\uninstall.exe TRAXSTAR"

The program also has no native way of switching between dev and prod environments, so I wrote another wrapper that overwrites the values in the registry with certain IP addresses depending on which environment the user wants to be in.

# set everyone full-control to traxstar registry key. this is a hack to allow
# switching between dev and prod environments
$acl = Get-Acl 'HKLM:\SOFTWARE\Wow6432Node\TraxStar Technologies LLC'
$rule = New-Object System.Security.AccessControl.RegistryAccessRule ("Everyone","FullControl","Allow")
$acl.SetAccessRule($rule)
$acl |Set-Acl -Path 'HKLM:\SOFTWARE\Wow6432Node\TraxStar Technologies LLC'

Make a folder in C:\program files

# create install folder (and yes, traxstar has to be in the x64 folder even though it's
# x86 or it will break
mkdir "C:\program files\traxstar\traxclient"

Grant everyone rights to write to this folder

# qatrax requires write access to its own folder
icacls 'C:\program files\traxstar\traxclient' /grant:r everyone:f

Copy files to C:\program files

# copy qatrax files to program install folder
copy-item qatrax.exe "C:\program files\traxstar\traxclient"
copy-item traxlaunch.exe "C:\program files\traxstar\traxclient"
copy-item uninstall.exe "C:\program files\traxstar"
copy-item *.log "C:\program files\traxstar\traxclient"

Build out the start menu

# create start menu items
mkdir "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\QATrax"
icacls *.lnk /grant:r everyone:RX
copy-item *.lnk "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\QATrax"

  • Soli Deo Gloria

PowerShell Code to Find Default Printer

Here’s some code to query what computers have a certain default printer with PowerShell:

$PrinterNameSeek = "\\XXXXXXX01\XX_OFFICE_CLR_01"

$DefaultPrinterObject = Get-WmiObject -Query " SELECT * FROM Win32_Printer WHERE Default=$true"
$DefaultPrinterName = $DefaultPrinterObject.Name.ToUpper()

write-host $DefaultPrinterName

if ($DefaultPrinterName -eq $PrinterNameSeek)

{

write-host $env:COMPUTERNAME
write-host $env:username

out-file \\XXXXXX\logs\$($env:COMPUTERNAME).$($env:username).XX_OFFICE_CLR_01.txt

}

This will need to be run as the end user to use $env:username, but if your ExecutionPolicy doesn’t allow it, you can remove it.

After running this for a few days: you can do a “dir *.* > list.txt” and then import this into an Excel spreadsheet using the import data from text file feature.

  • Soli Deo Gloria

 

 

PowerShell Code to Replace Plain Text with in a Group of Files

Here’s some PowerShell code I wrote to replace the license server for Minitab 19:

# Ignore errors

$erroractionpreference = 'Continue'

# Change Minitab

$configFiles = Get-ChildItem "C:\ProgramData\Minitab" -filter *.ini -recurse -exclude *.dll, *.exe

foreach ($file in $configFiles)
{
 (Get-Content $file.PSPath) |
 Foreach-Object { $_ -replace "XXLIC02", "XXLIC09" } |
 Set-Content $file.PSPath
}

– Soli Deo Gloria

Enable Dell TPM Chip with Powershell

Here’s some Powershell code I used to enable the Dell TPM chip with Dell Command.  The Get-Laptop function was provided by https://blogs.technet.microsoft.com/heyscriptingguy/2010/05/15/hey-scripting-guy-weekend-scripter-how-can-i-use-wmi-to-detect-laptops/

The –% option (that’s dash-dash%) basically just says “Powershell, just pass these arguments along and don’t try to interpret them”.  This functionality requires Powershell v3 or later.

Probably would have been better to use Start-Process and check if the exitcode is not zero.  Note to use Dell Command to turn on the TPM chip you need to set a BIOS password and for 64-bit systems you need to use the 64-bit version of CCTK.

Function Get-Laptop
{
Param(
[string]$computer = "localhost"
)
$isLaptop = $false
if(Get-WmiObject -Class win32_systemenclosure -ComputerName $computer |
Where-Object { $_.chassistypes -eq 9 -or $_.chassistypes -eq 10 `
-or $_.chassistypes -eq 14})
{ $isLaptop = $true }
if(Get-WmiObject -Class win32_battery -ComputerName $computer)
{ $isLaptop = $true }
$isLaptop
} # end function Get-Laptop

If(get-Laptop) {

.\cctk.exe –% –setuppwd=secretpassword
.\cctk.exe –% –tpm=on –valsetuppwd=secretpassword
.\cctk.exe –% –tpmactivation=activate –valsetuppwd=secretpassword
.\cctk.exe –% –tpm
.\cctk.exe –% –tpmactivation
.\MbamClientSetup.exe –% /q /acceptEula=Yes
}

else { # do nothing }

}

-Soli Deo Gloria

Deploy Infor XA Client with PowerShell

Here was a fun installer to get working silently.  This one uses something called InstallAnywhere.  It is a java based installer and if you Google InstallAnywhere silent, you will happen upon several command line options.  The correct set for this version of the installer (2009 version) can be found here.

Here’s the command to install it silently:

xaclient_Hgenas400_P36001.exe -i silent

We can also record settings into a file and play those back.  To record:

xaclient_Hgenas400_P36001.exe -r C:\temp\powerlink.properties

Finally, we end up with this to install silently:

xaclient_Hgenas400_P36001.exe -i silent -f powerlink.properties

The installer launches the program at the end: I didn’t see any settings to turn that off.

The PowerShell code starts off pretty boring:

$p = start-process .\xaclient_Hgenas400_P36001.exe -ArgumentList '-i silent -f powerlink.properties' -Wait -Passthru
icacls *.lnk /grant:r everyone:RX
copy-item *.lnk -Destination C:\users\public\desktop

We kick off the installer, tell PowerShell to wait for the process to end and return an object (-Passthru), grant Everyone read and execute rights to the icon and then copy that icon to the the system shared desktop.

If you execute this code, however, the PowerShell script never progresses. This is because the installer runs the full program as a child process from the installer and until the program is closed, it waits for the installer’s termination forever.

The program is Java based and executes two processes: Infor XA Power-Link and javaw.  We can create a loop waiting for these two processes, then kill them:

Do {

$status = Get-Process -Name "Infor XA Power-link" -ErrorAction SilentlyContinue

If (!($status)) {
Write-Host 'Waiting for process to start' ;
Start-Sleep -Seconds 2
}

Else { Write-Host 'Process has started' ;
$started = $true
Stop-Process -name "Infor XA Power-Link"
Stop-Process -name javaw
}

}
Until ( $started )

I picked 2 seconds to keep checking the process list and not hammer the CPU.  So obviously for this to work, we need to remove the -Wait and -Passthru options from Start-Process, but then how do we check if the program installed OK?  We can check if the program executable exists and then return the proper exit code:

if (Test-Path('C:\infor\ERP XA Client\Infor XA Power-Link.exe')) {
$LASTEXITCODE = 0
exit 0
} #end if

else {
$LASTEXITCODE = 1
exit 1
} #end else

Anything other than exit code 0 is usually a failure (MSIs usually return exit code 3010 to indicate a reboot).

Using the exit command with a specific number seems to pass the exit code properly back to SCCM.  Based on my Google-fu: it’s then best to wrap your PowerShell script in a batch file and then fire that from SCCM to get the exit code of any non-native command ran from within a PowerShell script:

powershell -executionpolicy bypass -file .\install_powerlink.ps1
echo %errorlevel%
exit /b %errorlevel%

  • Soli Deo Gloria

Powershell: Delete an Icon from All User Profiles

Started to learn Powershell recently and already found something really neat.  I’m working on deploying Smartdraw 2016 silently and it loves to put an icon on the desktop of the user that it installing the program, not in C:\users\public\desktop where it belongs.  Now, with SCCM, this use to be very tricky, because when running installs they run under the SYSTEM account and not as the logged in user and the native DEL /S command within the native CLI will do it, however, there’s no way to specify just one folder to delete from: it will search all folders under all of the user profiles.

Based on a tip from https://www.sapien.com/blog/2014/10/16/delete-desktop-icons-a-windows-powershell-tip/, we can do this instead:

Remove-Item "C:\users\*\Desktop\smartdraw ci.lnk"

which basically just searches the desktop folder of each user profile instead of all folders in each profile and deletes the now defunct Smartdraw CI icon from each desktop folder.

And now instead of looking for the uninstall productcode string to feed to MSIEXEC /x to remove Smartdraw CI: the PowerShell App Deployment Toolkit includes this nifty cmdlet do all of the heavy leaving all in one line:

Remove-MSIApplications -Name 'Smartdraw'

  • Soli Deo Gloria