Powershell script - Start-Job & MSIEXEC - powershell

I wonder if you can help please? I have the need to write a Powershell script to execute an MSI script.
I also need to set a time out on the process (as the MSIs we're given sometimes hang).
I've seen that you can acheive this by using the Start-Job/ Wait-Job process
Obviously the code below in in a severe state of butchery currently
Thanks in advance
$timeoutSeconds = 20
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "My File" } | select UninstallString$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Vix.Cbe.SalesTransaction" } | select UninstallString
Echo "uninstall32 :" $uninstall32
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
$32p = #("/X", "$uninstall32", "/b")
}
Echo "uninstall32 :" $uninstall32
Echo "u32 :" $32p
$32j = Start-Job msiexec -ArgumentList $32p
if (Wait-Job $32j -Timeout $timeoutSeconds) { Receive-Process $32j }
Remove-Process -force $32j

You don't have to mess with jobs to do that. Here is the easy way:
start Notepad -PassThru | select -ExpandProperty id | set id
sleep 60
kill $id -ea 0
This might not work if app spawns another app and exits as the Id will be wrong. In that case you will probably have to hunt it in the process list or via cmd line params.

Thanks to majkinetor I managed to alter the code sufficiently to acheive my goal.
The only thing is that obviously whether the process is still actively uninstalling or not, its killed after the TOSecs value.
This should be good enough for what I need.
So to explain for anyone else looking for a similar solution:
This process checks both 32 bit and 64 bit Registry entries for an MSI similar to the ServiceName (Urbancode Deploy parameter is the '${p:ServiceName}' passed to the script at runtime)
If it finds an entry it'll execute the uninstall code for the specific 32/64 MSI
/x = Uninstall
$uninstall64/32 = GUID of the Uninstall part to the MSI
/nq = quiet uninstall with no GUI (infact in isolation testing you get a Yes/No dialogue)
The uninstall will run for the amount of seconds that you set in $TOSecs
Hope this helps someone else
$TOSecs = 30
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "'${p:ServiceName}'" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "'${p:ServiceName}'" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling 64bit " '${p:ServiceName}'
start-process "msiexec.exe" -arg "/X $uninstall64 /nq" -PassThru |
select -ExpandProperty id | set id
#Echo "proc id = "$id
sleep $TOSecs
kill $id -ea 0
}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling 32bit " '${p:ServiceName}'
start-process "msiexec.exe" -arg "/X $uninstall32 /nq" -PassThru |
select -ExpandProperty id | set id
#Echo "proc id = "$id
sleep $TOSecs
kill $id -ea 0
}

Related

Powershell DISM Progression bar

I have a problem, I am building a powershell script to restore / save wim images using the DISM command.
So I use a winform interface, with a progress bar.
However, I can't find a way to send the% information for my progress bar.
Currently I have done tests with my DISM command in fact for example >> c: \ test and I manage to recover the% but I cannot do them simultaneously.
Here is the code, do you have any idea?
Foreach ( $line in "c:\test.txt" )
{
$content = Get-Content -Path 'c:\test.txt'
$content
$count = ($content | Select-String -Pattern '%' -AllMatches)
$PCcomplete = $count.Count-1
}
Dism /Capture-Image /ImageFile:$chemin /CaptureDir:$choixlettre\ /Name:$nom /Compress:max /LogLevel:4 >> c:\test.txt
Finally, it's work ! But my progression bar stop at 96...97 the count forget some number..
$sb = [scriptblock]::Create("dism /apply-image /imagefile:$chemin /index:1 /applydir:t: /LogLevel:4 >>t:\test.txt")
$job = Start-Job -ScriptBlock $sb
#
$pccomplete = 0
$pccomplete1 = 0
# Boucle
while((Get-Job | Where-Object {$_.State -ne "Completed"}).Count -gt 0)
{
$content = get-content -Path 't:\test.txt'
$count = ($content | Select-String -Pattern '%' -AllMatches)
$PCcomplete = $count.Count
start-sleep -Second 2
if( $pccomplete1 -ne $pccomplete )
{
$pccomplete1 = $pccomplete
$richtextBox_history.AppendText("$PCcomplete`n")
$progressBar1.Value = $pccomplete
}

Can someone break down part of this Powershell script and explain what it does and how it works?

The Script:
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /quiet" -Wait
}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /quiet" -Wait
}
I understand that this part is looking for GUID in the registry for a display name that matches Dell SupportAssist.
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
Can anyone explain the rest, mainly the -replace and -arg commands?
The -replace commands are stripping out all the executable arguments from the uninstall string then storing what's left.
It then runs the msiexec executable using the arguments specified in the -arg string (/X switch, the uninstall string from previously and the /quiet switch)
If you'd formatted the script properly (which I've done for you) it would have been much simpler to see how it's working
Note that get-package and uninstall-package can do the same thing more easily. Oh, this is the same question, lol.

How can I call a command using the following output?

Please see the following command I'm running to obtain the GUID of a particular application. It works as expected as you can see from the screenshot underneath.
Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty |
Where-Object { $_.DisplayName -match "Dell SupportAssist" } |
Select-Object -Property DisplayName, UninstallString
What I'd like to do is call the uninstall command from the output GUID. So something like:
MsiExec.exe /X{OUTPUT}
or for this example
MsiExec.exe /X{95BD6E30-2B18-4FB0-B5AE-8250E5584831}
Can this be done?
I don't want to use a huge VBS script that I can't read that you found online, I'd like to understand it bit by bit ;)
I don't want to use Get-WmiObject Win32_Product - See https://gregramsey.net/2012/02/20/win32_product-is-evil/.
Your registry query returns a custom object with the uninstall command string as one of its properties. To invoke that command string you can create a scriptblock from that string and then invoke that scriptblock (e.g. via the call operator). Pipe the output of your PowerShell statements into a ForEach-Object loop like this:
... | ForEach-Object {
& ([Scriptblock]::Create($_.UninstallString))
}
You may try as #Ansgar mentioned something like:
Code edited with example for Dell Power*:
$DellSoftware = Get-ChildItem -Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall, HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall |
Get-ItemProperty |
Where-Object { $_.DisplayName -like "dell power*" } |
Select-Object -Property DisplayName, UninstallString
$DellSoftware.UninstallString -match '(?<MSI>\w+.exe) (?<Args>\S+)'
Start-Process -FilePath $Matches.MSI -ArgumentList $Matches.Args
If you check the $Matches variable it should contain:
Name Value
---- -----
Args /X{18469ED8-8C36-4CF7-BD43-0FC9B1931AF8}
MSI MsiExec.exe
0 MsiExec.exe /X{18469ED8-8C36-4CF7-BD43-0FC9B1931AF8}
It returns a hash table with the grouped matches. This is why I am being able to separate the executable into the MSI key and the arguments into the args key. Then starting the process calling the executable as a FilePath param and providing the args for the ArgumentList param.
Then the result:
What is happeinig here is that the property UninstallString, returns, as you may guess - String. You need to tell to the PowerShell to treat this string as an expression.
To do so you need to use the & symbol in front, putting the statement, that you would like to execute in round brackets.
Same behaviour could be observed, when you try to launch a file from a location that contains white space. Because of it, powershell puts it in brackets, and to be able to execute it, puts the & in front - i.e. & 'C:\Program Files\'
Hope that clarifies the case :)
If it's an msi, it's very easy with the package commands:
get-package *dell* | Uninstall-Package -whatif
EDIT:
If you want the exact command... (-force in case it asks to install nuget)
Uninstall-Package 'Dell SupportAssist' -Force
Guys my colleague found this on another Stackoverflow post:
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "Dell SupportAssist" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /quiet" -Wait
}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /quiet" -Wait
}
This works perfectly and includes switches for silent uninstall - It doesn't seem to use Win32_Product.
Any reason why this wouldn't be appropriate rolling out en masse compared to the suggestions above?

Powershell kill all Powershell windows that are open besides the current one

Get-Process powershell | Format-table -Property Id -HideTableHeaders -Force | out-string -OutVariable "a"
foreach ($a in $tre) {stop-process $tre}
It is much simpler, your script fragment foreach ($a in $tre) has the wrong order,
should be foreach ($tre in $a) but that wouldn't exclude the current powershell.
Try this:
Get-Process powershell | Where-Object ID -ne $PID | Stop-Process
$rtrtrt = (get-random)
Get-Process powershell | Format-table -Property name, Id -HideTableHeaders -OutVariable "zaz"
$zaz = $zaz | out-string
$zaz = $zaz -replace " ", " "
$zaz = $zaz -replace " ", " "
$zaz = $zaz -replace "powershell $pid", ""
$zaz=$zaz -replace "powershell", "stop-process -force"
echo $zaz >$env:temp\$rtrtrt.ps1
&"$env:temp\$rtrtrt.ps1"
IF(!(Test-Path "$env:temp\$rtrtrt.ps1")) {echo "#"} else {Remove-Item "$env:temp\$rtrtrt.ps1" -recurse}
I made that up but yours looks much better
thank you
enter code here
I'm gonna use yours obviously
Get-Process powershell | Where-Object ID -ne $PID | Stop-Process

Why is -like not working but -match does?

I got this script from here and it work for uninstalling an application, but only when -match is used instead of -like in the first 2 lines, even when I use the entire app name.
The app's name includes the version, so I'd like to wildcard the name in the script to support "MyApp 2.4.1", etc. Thanks!
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" |
foreach { gp $_.PSPath } | ? { $_ -like "MyApp*" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
foreach { gp $_.PSPath } | ? { $_ -like "MyApp*" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /qb" -Wait
}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /qb" -Wait
}
Get-ItemProperty produces an object, not a string. Check the DisplayName property instead of the object itself. You should also expand the uninstall string, so you don't need to use $uninstall64.UninstallString later.
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" |
foreach { gp $_.PSPath } |
? { $_.DisplayName -like "MyApp*" } |
select -Expand UninstallString
Consider the following string " MyApp 2.3.4" while will look at the important difference between the two examples you refer to:
Like ? { $_ -like "MyApp*" }
Match ? { $_ -match "MyApp*" }
-Like is looking for a string starting with "MyApp" followed by anything. -Match is looking for the text "MyApp" followed by any character. The -Like would fail since there is a preceding space. -Match treats "MyApp*" as a regex string which looks for "MyApp" followed by any character. In this case it cares not about the space to it matches. I suspect the -match would fail if you changed it too ? { $_ -match "^MyApp*" } as the caret says start of string.
If you want -like to work in this case you should change it to ? { $_ -like "*MyApp*" }
Important
While I am correct about why your comparison was not working Ansgar answer addresses the reason this issue was happening to you in the first place.