Powershell, registry and wildcards, oh my - powershell

Given...
HKLM\Software\
KeyName
Property_1
Property_2
Property_[0-1]
Key*Name
Property_1
Property_2
Property_[0-1]
Key#Name
Property_1
Property_2
Property_[0-1]
I can use
Get-Item -path:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name"
which will return KeyName, Key*Name and Key#Name, while
Get-Item -literalPath:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name"
will return just Key*Name. So far, so good. I can use -path or -literalPath as needed to either search for a key with wildcards or not. But properties pose a problem.
Get-ItemProperty -path:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\KeyName" -name:"Prop_[0-9]"
works as expected and returns Prop_1 & Prop_2 from the KeyName key. And
Get-ItemProperty -literalPath:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\KeyName" -name:"Prop_[0-9]"
works as expected and returns just Prop_[0-9] from the same key. But it all fails apart when you need to use a wildcard to find properties, in a path that includes a wildcard character as a literal in the key path. So...
Get-ItemProperty -path:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name" -name:"Prop_[0-9]"
returns Prop_1 & Prop_2 from all three keys. Not the desired behavior at all.
I had hoped to be able to filter on PSPath using -`literalPath' but this
Get-ItemProperty -literalPath:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name" -name:"Prop_[0-9]" | where {$_.PSPath -match [RegEx]::Escape("Key*Name")}
does not return the correct properties. It seems that a -literalPath means a literal name also. So I tried filtering on PSPath and Name like so
Get-ItemProperty -literalPath:"Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name" -name:"Prop_[0-9]" | where {(($_.PSPath -match [RegEx]::Escape("Key*Name")) -and ($_.Name -match "Prop_[0-9]"))}
But that doesn't work because once you actually get real properties, they are no longer a .NET type, they have been shat into a PSCustomObject.
And that is starting to get so complicated I wonder if there is a better way to proceed. I should note that the ultimate goal here is to get both a literal path and a list of literal property names, so that I can move, copy or delete the properties. So, given a path of Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name and a name of Prop_[0-9] I will eventually want to, for example, delete
HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name\Prop_1
&
HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name\Prop_2
but not
HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name\Prop_[0-9]
EDIT: Based on the answer from #Tomalak I have simplified a bit, to simply get back a list of property names. That looks like this
$keyPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name"
$propExpr = "Prop_[0-9]"
((Get-Item -literalPath:$keyPath | Get-ItemProperty).PSObject.Properties | Where-Object Name -Match $propExpr | ForEach-Object {$_.Name})

This will get a registry key by literal path and filter its properties by regex match
$keyPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Key*Name"
$propExpr = "Prop_[0-9]"
Get-Item -literalPath $keyPath -PipelineVariable key | Get-ItemProperty | ForEach-Object {
$_.PSObject.Properties | Where-Object Name -Match $propExpr | ForEach-Object {
[pscustomobject]#{
key = $key.Name
prop = $_.Name
value = $_.Value
}
}
}
Instead of the $key.Name you can of course return the actual $key if that's more convenient for your task.

Related

Filter PowerShell command Output

I want to change a mapped drive remote path. but I'm unable to filter the remote path property. Is there any way I can filter all the mapped drives remote path only so, later on, I can run a foreach loop to change the values? Thanks.
Get-Item -Path HKCU:\Network | Where-Object -FilterScript {'RemotePath'}
Hive: HKEY_CURRENT_USER\Network
Name Property
---- --------
Z RemotePath : \\IAPC\Users\IA\Documents\10
UserName :
ProviderName : Microsoft Windows Network
ProviderType : 131072
ConnectionType : 1
ConnectFlags : 0
DeferFlags : 4
UseOptions : {68, 101, 102, 67...}
PS HKCU:\Network>
Only interested in name of the network drive with RemotePath (under Property column)
The following outputs [pscustomobject] instances representing the values of those registry subkeys of HKCU:\Network whose RemotePath value is non-empty:
Get-ItemProperty -Path HKCU:\Network\* | Where-Object RemotePath
To get just the drive-name-remote-path pairs:
Get-ItemProperty -Path HKCU:\Network\* | Where-Object RemotePath |
Select-Object PSChildName, RemotePath
Note: The PSChildName property contains the drive letter of each mapping (it is the name of the subkey whose values are being returned).
To loop over all mappings of interest and update them by replacing the server-name component:
$oldServer = '\\IAPC\'
$newServer = '\\localhost\'
# CAVEAT: This instantly updates your drive mappings.
# You can add -WhatIf to the Set-ItemProperty call,
# to *preview* the operation, but it will only show the
# target registry key and value, not the new data.
Get-ItemProperty -Path HKCU:\Network\* | Where-Object RemotePath | ForEach-Object {
$driveLetter, $remotePath = $_.PSChildName, $_.RemotePath
Set-ItemProperty -LiteralPath HKCU:\Network\$driveLetter RemotePath ($remotePath -replace [regex]::Escape($oldServer), $newServer)
}
As for what you tried:
Where-Object -FilterScript {'RemotePath'} is a no-op, because any input meets the criterion 'RemotePath', which, as a non-empty string literal is invariably $true when interpreted as a Boolean. To access a property on the current input object, you need to use automatic $_ variable: { $_.RemotePath }
It is only with the simplified syntax, shown above, which doesn't use a script block ({ ... }) that the string argument given is implicitly interpreted as the name of the property to access on the input object at hand.
Get-Item -Path HKCU:\Network only targets the root key of all network mappings itself, not the subkeys that define the actual mappings.
In your case you're also interested in the RemotePath value of each subkey, which Get-ItemProperty provides for all subkeys, targeted with a wildcard pattern, HKCU:\Network\*
Get-ItemProperty, when not given a property name, returns all properties - which in the case at hand are registry values - as a "property bag", in the form of a [pscustomobject] instance, with the name of the containing key reported in the .PSChildName property.
Unfortunately, working with PowerShell's registry provider is often not as straightforward as one would like.
#imtiaz Hey i am not sure if you are trying the same but here is how i have achieved recently.
$oldServer = "\\abc.local"
$newServer = "\abc.com"
$paths = REG QUERY HKCU\Network | where{$_ -ne ""}
foreach ($item in $paths)
{
$oldPath = REG QUERY $item /f RemotePath /t REG_SZ | Out-String
$oldPath1 = $oldPath.Split()[-12]
$updatedPath = $oldPath1 -replace $oldServer,$newServer
reg add $item /v RemotePath /t REG_SZ /d $updatedPath /f /reg:64
}
I tried before Get-ItemProperty and set-itemproperty was facing some issues. This might help you. I know this's not a "professional way" but this worked for me.

Cannot Get-ItemProperty in Group Policy registry

I need to locate a specific GPO to manually delete it from our machines, due to the pandemic they are at home and outside domain, so I thought about doing it remotely via PS with Intune.
I'm trying to create an script that looks for the DisplayName of the GPO and the deletes it, but it seems like the properties are protected or some other issue, because i cannot find any Property beyond the Group Policy registry.
If i try to do:
Get-ChildItem -Path 'Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Group Policy\History\'
I get something like:
Name Property
---- --------
{3537}
{42B5}
{4CFB}
It does not matter how deep I go beyond that point, it does not show me any Property. I just started with PS and I don't know if there's anything I'm doing wrong, with others registries i got no issue.
¿Any thoughts? :(
At the end I want to have something like:
$path = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Group Policy\History\"
$match = "GPO_1234"
Get-ChildItem -Path $path -recurse |
ForEach { Get-ItemProperty $_.PSPath } |
Where-Object { $_.DisplayName -match $match } | del
But if it cannot match with anything if the Properties cannot be iterated.
Thanks in advance
It's a bit hard without knowing what's in "Group Policy\History" exactly (I have a single DWORD there and that's it, no subkeys at all), but assuming "DisplayName" is the name of a property somewhere and "GPO_1234" is the value of that property, then something like this should work:
$RegPath = "Registry::HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Group Policy\History\"
$Pattern = "GPO_1234"
Get-ChildItem -Recurse $RegPath |
ForEach-Object { Get-ItemProperty $_.PsPath } |
Where-Object {
$_.psobject.Properties.Name -eq 'DisplayName' -and
$_.psobject.Properties.Value -eq $Pattern
}
If you get the matches you want just throw a final | Remove-Item -Force at the end, and if you have any questions about what's going on just ask!
As a sidenote, you should avoid using aliases like Foreach and del and instead use the real nammes (ie. Foreach-Object and Remove-Item). It will make your scripts easier to read and follow in the long run.
Especially important with Foreach since it exists with that exact spelling but a completely different syntax as well (foreach ($Item in $Collection) {}).

can't seem to match two values in registry

I am trying to get all the NICs on my system and then using that information to insert registry values of *TCPChecksumOffloadIPv4 etc. However, I am failing this task miserably!
I can get all the GUID's and want to match that to what is in this registry path: HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\*
I get all the GUID's by this:
$GuidSet = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\*" | select -ExpandProperty pschildname
Output:
{1FE01120-3866-437F-81FF-556B08999AA4}
{2533855F-2A59-485D-87A0-167E5DA39E45}
{2A6471FB-C1D6-47D2-A665-9F276D142D7C}
{306D2DED-18B5-45D8-858E-BB3F49E3BD6A}
{30EF50B2-E4B3-400D-9614-B590E37DE4D8}
{4A208C06-0D99-4DE4-9B2F-86285AEF864E}
{B7883140-E15B-4409-BA1B-96E37A45425C}
{D129DDA8-C64B-46A1-B99A-EA74FC4FAF81}
{D5C9183B-E542-4010-866F-4443AD55F28C}
This is where I am stuck now...how can I use this information to match what is in the registry path of "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\*" ?
I tried the below but I get access denied - I think this is because of the "Properties" registry key - how can I ignore that registry key?
$path1 = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\*" |?{$_.NetCfgInstanceId -match $guidset} | select -ExpandProperty pspath
Once that is done though then do I construct a foreach loop on each entry and then add in the registry keys I need?
ANSWER:
you know what...when your in a muddle and you have lots of scripts...take a break, open a new window and start from scrath! That's what I did and in 10min I figured it out...!
$aGUID_SET = #(Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\services\Tcpip\Parameters\Interfaces\*" | select -ExpandProperty pschildname)
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\*" -exclude "Properties" |
Where-Object {$aGUID_SET.Contains($_.NetCfgInstanceId)} |
ForEach-Object {
""
$_.DriverDesc
$_.NetCfgInstanceId
}
You are on the right track.
The Get-ItemProperty cmdlet will only get the properties of the items specified, not including any sub-items.
Since the registry values you are looking for are not actually properties of the registry key HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318} but instead are properties of subkeys of that key, the first thing we need to do is list the subkeys: $path = "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}"
We can then use Get-ChildItem $path to list the subkeys.
After formatting the paths properly (add Registry:: to the front), you can then input that to Get-ItemProperty. I would filter with something like: Where-Object {$guidset -contains $_.NetcfgInstanceID} | Select-Object -ExpandProperty PSPath.
Finally, you should have an array of paths to keys that matched $guidset, which
Set-ItemProperty can take.
EDIT: The error you are receiving is because permissions on those "Properties" subkeys is restricted. I would tack an -ErrorAction SilentlyContinue to Get-ChildItem because it is not a terminating error and does not actually affect the results.
You can do it like this
ForEach ($item in $(Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318}\*" |?{$_.NetCfgInstanceId -match $guidset} | select -ExpandProperty pspath)) {
Try {
Write-Host $item
} Catch {
Write-Host "error..."
}
}

Powershell - Strange output when using Get-ChildItem to search within files

I have a problem I am hoping someone could help with....
I have a powershell script containing the lines shown below:
$output = Get-ChildItem -path $target -recurse | Select-String -pattern hello | group path | select name
Write-Output "Output from the string match is $output"
The error I am getting:
Output from the string match Microsoft.Powershell.Commands.GroupInfo Microsoft.Powershell.Commands.GroupInfo
When I run this command on it's own (ie not within a script) it works perfectly and returns the two files in that location that contains the word "hello".
It appears that it knows there are two things it has found because it prints the "Microsoft.Powershell.Commands.GroupInfo" text twice (as shown above in the error). But why is it printing this and not the path to the files as it should do?
There must be something obvious I am overlooking but I dont know what.
Your help is much appreciated, thanks
The reason you're seeing this is because $output is an array of Selected.Microsoft.PowerShell.Commands.GroupInfo objects -- the objects returned by Group-Object when passed to Select-Object (without Select-Object they would just be Microsoft.PowerShell.Commands.GroupInfo objects instead). You can confirm the type of objects in $ouput by running:
$output | Get-Member
Check the TypeName that is displayed at the top of the output.
When you run these commands interactively in the console, you are seeing the paths because PowerShell knows how to display GroupInfo objects in the console so that they are human-readable. Note that when you just call $output in the console, you see a "Name" header underlined with dash characters -- this is PowerShell interpreting the GroupInfo object you gave it and displaying the Name property for you in the console.
The problem occurs when you try to output the $output array inside a string. Then PowerShell is not able to use its more advanced formatting logic and instead merely tries to convert the object to a string to insert into your string. When it does that, it doesn't have enough logic to know that what you really want to appear in your string is the Name property of these GroupInfo objects, so instead if just prints out a string with the type name of each of the objects in the $output array. So that's why you see the type name twice.
The simple solution to this problem is the -ExpandProperty parameter for Select-Object. This does what it says -- it expands the property you asked for with Select-Object and returns just that property, not the parent object. So the Name property of a GroupInfo object is a string. If you call Select-Object Name, you get a GroupInfo object with the Name property. If you call Select-Object -ExpandProperty Name, you get just the Name property as a String object. Which is what I expect that you want in this case.
So try this instead:
$output = Get-ChildItem -path $target -recurse | Select-String -pattern hello | group path | select -ExpandProperty name
A foreach would be appropriate here I believe. Try this:
$output = Get-ChildItem -path $target -recurse | where {$_.name -like "*hello*"} | select name
foreach ($file in $output) {
write-host $file.name
}
Or this:
$output = Get-ChildItem -path $target -recurse | select-string -pattern "hello" | select name
foreach ($file in $output) {
write-output $file.name
}

how to filter name/value pairs under a registry key by name and value in PowerShell?

I'm trying to get a feel for the idioms to use in PowerShell.
Given this script:
$path = 'hkcu:\Software\Microsoft\Windows\CurrentVersion\Extensions'
$key = Get-Item $path
$key
I get the output at the bottom of this question.
I'd like to get output of the properties (the name/value pairs under the $key) where I can filter on both name and value.
For instance, filter to list all the Extensions that have:
name like xls*
or value like *\MSACCESS.EXE
Or an exclude filter: exclude all names like doc*
Fir the first filter, I'd want a result like this:
Name Value
---- --------
xlsx C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
This is the original output of the script:
Hive: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
Name Property
---- --------
Extensions rtf : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.rtf
dot : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dot
dotm : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dotm
dotx : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dotx
docm : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.docm
docx : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.docx
doc : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.doc
xlsx : C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls : C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb : C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda : C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
Edit
I solved part of the problem: getting a list of Name/Value pairs. It uses PSCustomObject:
$namevalues = $key.GetValueNames() | ForEach-Object { [pscustomobject]#{ Name=$_; Value=$key.GetValue($_) } }
$namevalues
(How should I wrap that code?)
Any help with the filtering would be much appreciated
A two part answer.
We start with the $key from the registry:
$path = 'hkcu:\Software\Microsoft\Windows\CurrentVersion\Extensions'
$key = Get-Item $path
$key
$key | Get-Member
Since $key is a Microsoft.Win32.RegistryKey, you cannot get the name value pairs out directly.
The first step is to create a list of PSCustomObjects with Name/Value pairs. The Name comes from the GetValueNames piped through a ForEach-Object. For each of those Names, we get the Value through GetValue:
$namevalues = $key.GetValueNames() |
ForEach-Object {
[PSCustomObject] #{
Name = $_;
Value = $key.GetValue($_)
}
}
$namevalues | Format-Table
An alternative for the first step is to use the Select-Object using -ExpandProperty as explained by Scott Saad:
$namevalues = $key | Select-Object -ExpandProperty Property |
ForEach-Object {
[PSCustomObject] #{
Name = $_;
Value = $key.GetValue($_)
}
}
$namevalues | Format-Table
The second step is to filter the $namevalues either by Name or by Value.
The Where-Object has some pretty cool Comparison operators that accept regular expressions like match, notMatch, etc.
To make the code more readable, you can wrap lines (thanks Joey!) either use the backtick (`) or take advantage of the places in the PowerShell syntax where it does accept line breaks, like after a pipe (|) or opening brace ({):
$matches = $namevalues |
Where-Object {
$_.Name -match '^xls' `
-or $_.Value -match 'msaccess.exe$'
}
$matches | Format-Table
The result is as wanted:
Name Value
---- -----
xlsx C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
There's a much more clever way to enumerate registry values (found it here). And it's more powershell-way IMO.
I've turned it into a one-liner:
(Get-ItemProperty $path).psobject.properties |
where {$_.name -like "xls*" -or $_.value -like "*\MSACCESS.EXE"} |
select name,value
Update: As noted in comments by #mklement0, you should be careful about properties PSPath, PSParentPath, PSChildName, PSDrive, and PSProvider within the psobject.properties.
A more robust PSv4+ alternative to montonero's helpful answer:[1]
Get-Item $path -PipelineVariable key | ForEach-Object Property | ForEach-Object {
$name = ($_, '')[$_ -eq '(default)'] # translate '(default)' to '' for API calls
if ($name -like 'xls*' -or ($value = $key.GetValue($name)) -like "*\MSACCESS.EXE")
{ [pscustomobject] #{ Name = $name; Value = $value } }
}
-PipelineVariable key stores the [Microsoft.Win32.RegistryKey] instance returned by Get-Item in variable $key for later use in the pipeline.
ForEach-Object Property enumerates the target key's value names (via the .Property note property that PowerShell adds to the output [Microsoft.Win32.RegistryKey] instance).
Inside the Where-Object script block, $_ then refers to the value name at hand, and $key.GetValue(<valueName>) is used to retrieve the associated data.
Important: In the .Property array, PowerShell translates the default value name, which is the empty string ('') at the API level, into name '(default)'; thus, if $_ is '(default)', you must translate it to '' before calling $_.GetValue(<valueName>), which is what ($_, '')[$_ -eq '(default)'] does.
[pscustomobject] #{ ... } then constructs and outputs a [pscustomobject] instance with .Name and .Value properties reflecting the matching value's name and data.
[1] montonero's answer is concise and works well in the case at hand, but it comes with caveats:
PowerShell's registry provider automatically adds the following additional note properties (members of type NoteProperty, as reflected in the output from Get-Member) containing metadata about the targeted registry keys to the [pscustomobject] instance that Get-ItemProperty outputs:
PSPath, PSParentPath, PSChildName, PSDrive, PSProvider
These can interfere with filtering based on .psobject.properties in two ways:
Using wildcard matching against $_.Name can accidentally include these properties.
E.g., $_.Name -like '*drive*' would match the PSDrive property, even though it isn't actually part of the registry key.
Perhaps more hypothetically, if the registry key happens to have values of the same name as these provider properties, the provider properties shadow (override) these values.
E.g., if the key has a PSPath value, $_.Value will report the provider property value instead.