With help of Powershell I need to find registry key, where Value Displayname like 'Cisco', and get from this key data from Value name 'Uninstallstring'.
I know this sounds somewhat strange, but this application has a different uninstall string on each computer.
So
ForEach-Object -InputObject (Get-ChildItem 'HKLM:\software\microsoft\windows\currentversion\uninstall') {
$single_item = $_ | Get-Item
$single_item_properties = $single_item | Get-ItemProperty | Select-Object -Property DisplayName,UninstallString | Where-Object {($_.DisplayName -like '*Cisco*TSP*')}
$uninstall_str = ($single_item_properties | Select UninstallString)
$str_to_execute=$uninstall_str.UninstallString -replace '" .*','"'
$str_to_execute
Start-Process -FilePath $str_to_execute -ArgumentList '-s','-f1"\\sandbox\Common.Installs\Utils\un.iss"' -Wait -PassThru
}
This script gives us the error
UninstallString
"C:\Program Files (x86)\InstallShield Installation Information{01A05F96-E34D-4308-965C-65DCA4AF114D}\setup.exe"
Start-Process : This command cannot be executed due to the error: The system cannot find the file specified.
The problem is that the result is in not String type.
And I can't convert it into String.
There are several issues here.
Although ForEach-Object does have the -InputObject parameter, it's primarily designed to take pipeline input. As the documentation says,
When you use the InputObject parameter with ForEach-Object, instead of piping command results to ForEach-Object, the InputObject value—even if the value is a collection that is the result of a command, such as –InputObject (Get-Process)—is treated as a single object. Because InputObject cannot return individual properties from an array or collection of objects, it is recommended that if you use ForEach-Object to perform operations on a collection of objects for those objects that have specific values in defined properties, you use ForEach-Object in the pipeline
In your code, the scriptblock gets executed only once, and $single_item is actually an array containing the entire output of Get-ChildItem. In order to iterate over the results one element at a time, you need to either pipe the output to ForEach-Object...
Get-ChildItem 'HKLM:\software\microsoft\...' | ForEach-Object {
...or better yet, use the foreach control structure, which generally runs faster (though it uses more memory):
foreach ($single_item in (Get-ChildItem 'HKLM:\software\microsoft\...')) {
Piping each element to Get-Item is superfluous. You can pipe directly to Get-ItemProperty.
You don't need to select a property in order to filter on it with Where-Object. So, it's superfluous to use Select-Object -Property DisplayName,UninstallString, which creates a PSCustomObject with two properties, and then retrieve the UninstallString property of that PSCustomObject later in the code. Just filter with Where-Object first, then get the value of UninstallString with | Select -ExpandProperty UninstallString.
(See this answer for an explanation of the reason for using the -ExpandPropery switch.)
Using -ExpandProperty, you'll get errors if any keys are returned that do not have an UninstallString property, so you might want to add -ErrorAction SilentlyContinue to your Select-Object statement.
Putting it all together:
foreach ($single_item in (Get-ChildItem 'HKLM:\software\microsoft\windows\currentversion\uninstall')) {
$uninstall_str = $single_item `
| Get-ItemProperty `
| ?{$_.DisplayName -like '*Cisco*TSP*'} `
| Select-Object -ExpandProperty UninstallString -ErrorAction SilentlyContinue
$str_to_execute = $uninstall_str -replace '" .*','"'
Start-Process -FilePath $str_to_execute -ArgumentList '-s','-f1"\\sandbox\Common.Installs\Utils\un.iss"' -Wait -PassThru }
}
If there's guaranteed to be no more than one matching item, you can do it more compactly like this:
$str_to_execute = (
Get-ChildItem 'HKLM:\software\microsoft\windows\currentversion\uninstall' `
| Get-ItemProperty `
| ?{$_.DisplayName -like 'Microsoft*'}
).uninstallstring $uninstall_str -replace '" .*','"'
The more compact version will actually work even if there are multiple matching keys in PowerShell v3+, but in v2 it would return null if there's more than one.
BTW, ? is an abbreviation for Where-Object. Similarly, % is an abbreviation for ForEach-Object (only if you use it as a filter in a pipeline, but that's how you should be using it anyway.)
The output you show does not seem to match the code you provided. When I execute your code I get the uninstallstring based on my own search. When I first saw the output I thought "Oh... he forgot that he was passing an object with a property UninstallString to Start-Process". But when I looked at your code I could see that you accounted for that by doing $uninstall_str.UninstallString. So I am not sure what is exactly wrong but one improvement would be to use -ExpandProperty of Select-Object
$uninstall_str = $single_item_properties | Select -ExpandProperty UninstallString
$str_to_execute=$uninstall_str -replace '" .*','"'
But we can make a one liner if you prefer.
$str_to_execute = ($single_item_properties).UninstallString -replace '" .*','"'
Related
This is the command I am invoking:
Get-ChildItem | where -Property name -like *asda* | select -first 1 | $_.Name
Obviously this call at the end doesn't work because the $_ only works for iterable loops. But I want to pick that element of the list and turn it into a object where I can call auto complete (ctrl + space).
How can I achieve that in Powershell?
Do you try ?
(Get-ChildItem | where -Property name -like *asda* | select -first 1).Name
JPBlanc's answer is effective, but let me dig a little deeper:
For a streaming solution - where each object being emitted by Get-ChildItem is processed one by one, as it is being emitted - use a ForEach-Object call with the same simplified syntax you're using with the Where-Object (where) cmdlet:
Get-ChildItem |
Where-Object -Property Name -like *asda* |
Select-Object -First 1 |
ForEach-Object -MemberName Name
Note: Alternatively, you could use Select-Object -ExpandProperty Name.
For a more concise solution - which involves collecting all Get-ChildItem output up front - use the following:
((Get-ChildItem).Name -like '*asda*')[0]
I have a working script for detecting Log4j but trying to export all the result to CSV.
The current script was able to export but is giving me multiple rows of exact result.
$hostnames = "PC1"
foreach ($hostname in $hostnames){
Write-Host "Starting..."
$List = Invoke-Command -ComputerName $hostname {Get-ChildItem 'C:\' -Recurse -Force -Include *.jar -ea 0 | foreach {select-string "JndiLookup.class" $_} | select -exp Path}
if ($List -ne $null)
{
$List1 = [system.String]::Join("`r`n", $List)
$file_location = "C:\Powershell\log4j-test.csv"
$global:name = #{N="Computer Name";E={$hostname}}
$global:lists = #{N="Affected Location";E={$List1}}
Write-Host "Done.."
$name, $lists | Export-CSV $file_location -noTypeInformation -Force -Append -Encoding UTF8
}
}
You're probably looking for something like this:
[pscustomobject] #{
'Computer Name' = $hostname
'Affected Location' = $List -join "`r`n"
} | Export-CSV $file_location -noTypeInformation -Force -Append -Encoding UTF8
Note that each single CSV row created in each iteration will (potentially) span multiple lines, due to using newlines ("`r`n") as separators in the Affected Location column.
As for what you tried:
Your hashtable literals (e.g. #{N="Computer Name";E={$hostname}}) use the syntax of a calculated property, yet you're not using a cmdlet - typically Select-Object - that is capable of creating calculated properties.
More generally, Export-Csv and ConvertTo-Csv in Windows PowerShell do not meaningfully support hashtables as input objects - you'll need to provide [pscustomobject] instances instead, which you can easily create by casting[1] a hashtable literal to [pscustomobject], as shown above
By contrast, in PowerShell (Core) 7+ you may now use hashtables directly as input, though you'll probably want to use [ordered] hashtables for predictable column ordering.
[1] Strictly speaking, the form [pscustomobject] #{ ... } isn't a cast, but built-in syntactic sugar for creating [pscustomobject] instances as literals. This is evidenced by the fact that the key-definition order in the hashtable literal is maintained in this case, whereas it isn't in stand-alone hashtable literals.
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?
I am attempting to create something along the lines of the following:
Get-ChildItem -path *.txt* -recurse | $a = $_.Name.Length | Format-Table Name, $a
This is a contrived example, but assume that I wanted to make use of $_Name.Length several times, but substitute $1 or $a in its place.
How do I accomplish this?
Use an intermediate calculated property:
Get-ChildItem -Path *.txt* -Recurse | Select-Object *, #{ n='a'; e={ $_.Name.Length } } |
Format-Table Name, a
Note:
This uses a property on the output object rather than a variable to carry the value of interest.
In doing so, it changes the type of the objects passed through the pipeline to type [pscustomobject] - a custom type with the added a property. This may or may not be a problem (not a problem with piping to Format-Table).
By contrast, if your plan is to simply process the input objects in a ForEach-Object process block anyway, you can simply define a variable inside that block:
Get-ChildItem -Path *.txt* -Recurse | ForEach-Object {
$a=$_.Name; $a.Substring(0, $a.IndexOf('.txt'))
}
(As an aside: This example command doesn't strictly need a helper variable, because it can more succinctly expressed as:
Get-ChildItem -Path *.txt* -Recurse | ForEach-Object { $_.Name -replace '\.txt.*$' }).
Get-ChildItem -Path E:\Server_Data\data\2015 -Recurse –File -include "*.txt","*.csv" | Where-Object {$_.Name -like "*transaction*"} | Select-Object -ExpandProperty FullName,LastWriteTime
I'm trying to list all files in a folder using Get-ChildItem and Select-Object property. When I try to use FullName variable to list the fully qualified file name, the file name is getting truncated. Tried to use -ExpandProperty to get fully qualified file name. It works for one field but if I try to list both FullName and LastWriteTime, it's not working.
The output from the power shell command will be used in MS SQL Server to load the file names into a specific table.
Please suggest proper syntax for my purpose. Appreciate your help!
Depending on your use case and input, one way to accomplish this is by having two Select-Object cmdlets in your pipeline, one to define an array of properties, and one to expand them:
PS C:\> $Name,$DisplayName,$Status = Get-Service
| Select-Object -First 1 -Property #{
Name = "MyProperties"
Expression = { $_.Name,$_.DisplayName,$_.DisplayName }
} | Select-Object -ExpandProperty MyProperties
Get-ChildItem -Path E:\Server_Data\main.sweetbeam.net\data\2015 -Recurse –File -include "*.txt","*.csv" | Where-Object {$_.Name -like "*transaction*"} | Select-Object #{Name="test";Expression={$_.FullName + "`t" + $_.LastWriteTime}} | Select-Object -ExpandProperty test
Merged the two fields and used the derived field for my purpose.
If you are only going to do this to display on the console, then you can look at using Format-Table with the -Wrap parameter to display all of the text on the console without truncating the output.
Get-ChildItem -Path E:\Server_Data\data\2015 -Recurse –File -include "*.txt","*.csv" |Where-Object {
$_.Name -like "*transaction*"
} | Format-Table FullName,LastWriteTime -Wrap
If you are planning on doing anything else with our output, then you will need to avoid the use of Format-* cmdlets as they add custom formatting to the object and render it effectively useless in the pipeline.
If you're using the output in another program, you should be converting to CSV, JSON, or XML rather than copying the formatted output. It may also be possible to insert directly into SQL using PowerShell commands.
Get-ChildItem -Path E:\Server_Data\data\2015 -Recurse –File -include "*.txt","*.csv" |
Where-Object {$_.Name -like "*transaction*"} |
Select-Object FullName,LastWriteTime |
ConvertTo-Csv -NoTypeInformation
Other options for the last command are:
JSON with ConvertTo-Json
XML with ConvertTo-xml -As String -NoTypeInformation
Late answer, but this has existed since PowerShell 3.0, so should have been an option.
get-eventlog Security `
-EntryType SuccessAudit `
-Newest 50 `
-InstanceId 4624,4634 `
-ComputerName $env:COMPUTERNAME |
Select-Object -Property *
OR (not all fields)
get-eventlog Security `
-EntryType SuccessAudit `
-Newest 50 `
-InstanceId 4624,4634 `
-ComputerName $env:COMPUTERNAME |
Select-Object -Property TimeGenerated,EventID,Message -PipelineVariable myEvent |
Foreach-Object -Process {
convertTo-Json -InputObject #{
TimeGenerated = (Get-Date $myEvent.TimeGenerated)
EventID = $myEvent.EventID
Message = $myEvent.Message.Substring(0, $myEvent.Message.IndexOf("`r"))
} -Depth 1
}
Hope I'm not late for the game. This one example works for me (Get-Service, more than 1 properties having embedded 'collections' of data)
Get-Service |
Select-Object
Name,
Status,
...
and then in the next lines in your script, the next object(s) that you want expanded should be like this
#{Name="CustomCol01";Expression={$_.ServicesDependedOn} },
#{Name="CustomCol02";Expression={$_.DependentService} }
In the example, the 2 offending objects that I need expanded are ServicesDependedOn and DependentService
So yeah by custom making new columns it expands whatever Objects for you
Tested works even when redirecting the final output to a csv like this
... > myfile.csv