Why is -like not working but -match does? - powershell

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.

Related

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 Script Loop Through Each File And Do A Find And Replace

My Current Code Is
Param(
[string]$filePath = "C:\",
[string]$logFileFind = "error.log",
[string]$logFileReplace ="ThisHasBeenReplaced.log"
)
($configFile = Get-ChildItem -Recurse -Force $filePath -ErrorAction SilentlyContinue | Where-Object { ($_.PSIsContainer -eq $false) -and ( $_.Name -like "*.config") }
It Works fine and gives me list of files i was wondering how i could go through these files and find and replace certain words for when I'm moving though environments and the path wont be the same. I'm very limited in my powershell knowledge and i tried adding this to the end of the script.
ForEach-Object{(Get-Content $configFile) -replace $logFileFind , $logFileReplace | Set-Content $configFile})
This didn't work and i was wondering if there was anyone out there who knew what i could do to make it work.
Thanks in Advance!
You always access $configFile in your foreach loop (which is probably an System.Array), not the actual element. Try this:
$configFile | foreach { (get-content $_.FullName -Raw) -replace $logFileFind , $logFileReplace | Set-Content $_.FullName }
Here is a full example:
Get-ChildItem -Recurse -force $filePath -ea 0 |
where { ($_.PSIsContainer -eq $false) -and ( $_.Name -like "*.config") } |
foreach {
(gc $_.FullName -raw) -replace $logFileFind , $logFileReplace | sc $_.FullName
}

OR and AND operator inside ? filter

I have a long program which does as such:
[xml]$data = get-content "c:\somedatafile.xml"
$data.PRINTERS.PRINTER `
| ? { $_.USERS.USER -eq "$env:username" } `
| ForEach-Object{
# install some printer with $_
}
I have also some COMPUTERS.COMPUTER to be compared with $env:computername.
How can I install the printer if at least one of the COMPUTERS.COMPUTER -eq "$env:computername"? Or how do I include OR and AND, and possibly () inside the ? filter.
Where can the meaning of the ? filter be found out (Google fails with monocarachter queries)?
Suggestions about code sanity are welcome.
Please see Using the Where-Object Cmdlet
Get-Process | Where-Object { ($_.handles -gt 200) -and ($_.name -eq "svchost") }
Note that there is no associative operator is () (as not written on this page).
Here your snippet more readable:
[xml]$data = get-content "c:\somedatafile.xml"
$data.PRINTERS.PRINTER | Where-Object -FilterScript { $_.USERS.USER -eq "$env:username" } | ForEach-Object {
# install some printer with $_
}
No you have to add an neq If statement in the foreach and check if COMPUTERS.COMPUTER -eq "$env:computername". If true, install the driver.

Write parts from filename including the first line in merged txt / csv -file with powershell

I'm currently working on a PowerShell script that reads out the default printer on several workstations and write the information in a textfile to a network drive. My last question regarding some text replacements inside the script was successfully solved. But now I work on the second part.
Get-WmiObject -Class Win32_Printer -Filter "Default = $true" | % {
$_.Name -replace '(?:.*)\\NDPS-([^\.]+)(?:.*)', 'PS-$1'
} | Out-File -FilePath "H:\daten\printer\$($env:COMPUTERNAME)_defaultprinter.txt"
Get-WmiObject Win32_Printer -Filter "Default = $true" `
| Select-Object -expandProperty Name `
| Out-File -FilePath "P:\batch\migration\Printer\$($env:COMPUTERNAME)_$($env:USERNAME)_defaultprinter.txt"
The last line of the provided code writes the default printer to the network drive. Now I have there nearly 1500 single txt-files. For better analysis I use the following PowerShell script to merge all the single txt files to one big file.
Get-ChildItem -path \\samplepath\prgs\prgs\batch\migration\Printer -recurse | ? {
! $_.PSIsContainer
} | ? {
($_.name).contains(".txt")
} | % {
Out-File -filepath \\samplepath\prgs\prgs\batch\migration\Printer\gesamt_printer.txt -inputobject (get-content $_.fullname) -Append
}
I receive a file wich contains the default printer information from every txt-file but I need the $($env:USERNAME)-part from the filename as a separate value in addition to the printer information in on line to use the data in Excel. Can someone please provide me a tip how to insert the part from filename in the merged file?
You could extract the username part from the file name like this:
$_.Name -match '^.*?_(.*)_.*?\.txt$'
$username = $matches[1]
The group in the regular expression (accsisible via $matches[1]) contains the text between the first and the last underscore in the filename.
You could use it like this:
$root = "\\samplepath\prgs\prgs\batch\migration\Printer"
$outfile = "$root\gesamt_printer.txt"
Get-ChildItem $root -Recurse | ? {
-not $_.PSIsContainer -and $_.Extension -eq ".txt"
} | % {
$_.Name -match '^.*?_(.*)_.*?\.txt$'
$username = $matches[1]
$content = Get-Content $_.FullName
"{0},{1}" -f ($content, $username) | Out-File $outfile -Append
}
You could also directly create a CSV:
$root = "\\samplepath\prgs\prgs\batch\migration\Printer"
$outfile = "$root\gesamt_printer.txt"
$getPrinter = { Get-Content $_.FullName }
$getUser = { $_.Name -match '^.*?_(.*)_.*?\.txt$' | Out-Null; $matches[1] }
Get-ChildItem $root -Recurse `
| ? { -not $_.PSIsContainer -and $_.Extension -eq ".txt" } `
| select #{n="Username";e=$getUser},#{n="Printer";e=$getPrinter} `
| Export-Csv $outfile -NoTypeInformation
Note that these code sample don't contain any checks to exclude file names that don't have at least two underscores in them.