Search Registry and create New-Item - powershell

I want to create a New-Item in the registry with a pre check what already exists.
This code
$items = get-item "HKCU:\SOFTWARE\Microsoft\Office\16.0\Excel\Options"
$items.Property -match "OPEN"
returns the following
OPEN
OPEN1
OPEN2
OPEN3
OPEN4
Now I know I need to create a New-Item with the name OPEN5, but how do I count through this? Maybe with a While-Loop?

The most robust approach is to extract the embedded numbers, sort them numerically, and add 1 to the highest index to date:
$nextNdx = 1 +
([int[]] ($items.Property -match '^OPEN' -replace '\D') | Sort-Object)[-1]
$items.Property -match '^OPEN' -replace '\D' returns all property names that start with OPEN and removes all non-digit characters from them (-replace '\D').
[int[]] converts the resulting "number strings" to actual numbers ([int]); note that casting '' or $null to [int] in PowerShell yields 0.
Sort-Object sorts these numbers, and [-1] grabs the last number from the resulting array, i.e., the highest number.
The above is convenient, but not fast, due to use of the pipeline and the Sort-Object cmdlet.
If you want to avoid the pipeline for performance reasons:
$indices = [int[]] ($items.Property -match '^OPEN' -replace '\D')
[Array]::Sort($indices) # sort in place
$nextNdx = 1 + $indices[-1]

Related

Powershell - Need to take only file name with a specific pattern and ignore the rest

I have a script that read a file name from path location and then he takes only the numbers and do something with them. could be more than one file in the path
An example for file:
Patch_1348968.vip
Patch_1348968_v1.vip
Patch_1348968_v2.Zip
It takes the number 1348968.
The code that do that is:
$compressedfiles = Get-ChildItem -path $FilesPath\* -Include "*.vip", "*.zip", "*cab"
foreach ($file in $compressedfiles) {
$PatchNumber = ""
$PatchNumber = $file.Name -replace '.*[-_](\d+).*', '$1'
more code....
}
The goal is to ignore (continue to the next file) while the pattern is not as patch_#########
An example to files I want to ignore:
patch-8.6.22 (DA GUI CU + 1351661 + 1344344).zip
Any idea how to do it?
Thanks
Your regex is too permissive to exclude the files that are not of interest.
Based on the examples, including in later comments, and your description, the following would work:
# Simulated Get-ChildItem output.
$compressedfiles = [System.IO.FileInfo[]] #(
'patch-8.6.22 (DA GUI CU + 1351661 + 1344344).zip',
'Patch_1348968.vip'
'Patch_1348968_v1.vip',
'Patch_1348968_v2.Zip',
'patch-1234567.zip',
'patch_7654321-V9.zip'
'patch-7654329-V10.zip',
'patch_42424242_abc453.zip',
'patch_42424243_copy#34.zip',
'Patch_1348968_copy.Zip'
)
foreach ($file in $compressedfiles) {
if ($file.Name -notmatch '^patch[-_](\d+)(?:[-_][\w]+#?\d*)?\.\w{3}$') {
Write-Verbose -Verbose "Skipping: $($file.Name)"
continue
}
$patchNumber = $Matches[1] # Get the number that the capture group matched.
$patchNumber # Sample output.
# ...
}
The above uses the -notmatch operator and the automatic $Matches variable instead of -replace, because the latter returns the input string as-is if its regex operand doesn't match (while you can compare the result to see if it is the same as the input string to infer whether -replace found at least one match, the above strikes me as conceptually clearer).
For an explanation of the regex and the ability to experiment with it, see this regex101.com page.
Use the -match operator to test if a string matches a given pattern. Extract the version number using a capture group (...) in the RegEx.
foreach ($file in $compressedfiles) {
if( $file.Name -match '^patch_(\d+)' ) {
$PatchNumber = $matches[1]
# more code....
}
}
The condition in the if statement evaluates to $true, when the pattern matches.
The pattern:
^ makes sure we match at the start of the file name, so names like foopatch_12345.zip won't match.
patch_ matches literally (case-insensitively by default)
( starts a capture group
\d+ matches one or more digits
) ends the capture group
For more information see the regex101 demo.
Using the automatic $matches variable, which contains the full match at index 0 and the matched values of any capture groups at subsequent indices, we extract the patch number. So $matches[1] is the value that matches the pattern \d+ within the parentheses.
Get-ChildItem -Path $path -Include "*patch*"

Sort-Object -Unique

I'm making a script that collects all the subkeys from a specific location and converts the REG_BINARY keys to text, but for some reason I can't remove the duplicate results or sort them alphabetically.
PS: Unfortunately I need the solution to be executable from the command line.
Code:
$List = ForEach ($i In (Get-ChildItem -Path 'HKCU:SOFTWARE\000' -Recurse)) {$i.Property | ForEach-Object {([System.Text.Encoding]::Unicode.GetString($i.GetValue($_)))} | Select-String -Pattern ':'}; ForEach ($i In [char[]]'ABCDEFGHIJKLMNOPQRSTUVWXYZ') {$List = $($List -Replace("$i`:", "`n$i`:")).Trim()}; $List | Sort-Object -Unique
Test.reg:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\SOFTWARE\000\Test1]
"HistorySZ1"="Test1"
"HistoryBIN1"=hex:43,00,3a,00,5c,00,54,00,65,00,73,00,74,00,5c,00,44,00,2e,00,\
7a,00,69,00,70,00,5c,00,00,00,43,00,3a,00,5c,00,54,00,65,00,73,00,74,00,5c,\
00,43,00,2e,00,7a,00,69,00,70,00,5c,00,00,00,43,00,3a,00,5c,00,54,00,65,00,\
73,00,74,00,5c,00,42,00,2e,00,7a,00,69,00,70,00,5c,00,00,00,43,00,3a,00,5c,\
00,54,00,65,00,73,00,74,00,5c,00,41,00,2e,00,7a,00,69,00,70,00,5c,00,00,00
[HKEY_CURRENT_USER\SOFTWARE\000\Test2]
"HistorySZ2"="Test2"
"HistoryBIN2"=hex:4f,00,3a,00,5c,00,54,00,65,00,73,00,74,00,5c,00,44,00,2e,00,\
7a,00,69,00,70,00,5c,00,00,00,43,00,3a,00,5c,00,54,00,65,00,73,00,74,00,5c,\
00,43,00,2e,00,7a,00,69,00,70,00,5c,00,00,00,44,00,3a,00,5c,00,54,00,65,00,\
73,00,74,00,5c,00,42,00,2e,00,7a,00,69,00,70,00,5c,00,00,00,41,00,3a,00,5c,\
00,54,00,65,00,73,00,74,00,5c,00,41,00,2e,00,7a,00,69,00,70,00,5c,00,00,00
The path strings that are encoded in your array of bytes are separated with NUL characters (code point 0x0).
Therefore, you need to split your string by this character into an array of individual paths, on which you can then perform operations such as Sort-Object:
You can represent a NUL character as "`0" in an expandable PowerShell string, or - inside a regex to pass to the -split operator - \0:
# Convert the byte array stored in the registry to a string.
$text = [System.Text.Encoding]::Unicode.GetString($i.GetValue($_))
# Split the string into an *array* of strings by NUL.
# Note: -ne '' filters out empty elements (the one at the end, in your case).
$list = $text -split '\0' -ne ''
# Sort the list.
$list | Sort-Object -Unique
After many attempts I discovered that it is necessary to use the Split command to make the lines break and thus be able to organize the result.
{$List = ($List -Replace("$i`:", "`n$i`:")) -Split("`n")}

powershell filter filenames with regex

I am building a list of files that I'm putting into my $list variable.
Then I want to filter the list based on the $filter variable. The current solution works, but it doesn't work with a regex.
$filter = #("test.txt","Fake","AnotherFile\d{1..6}")
######### HTML TESTS #############
[string]$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
[array]$files = $list -split '\r?\n'
$files = $files | Where-Object {$_} | Where {$_ -notin $filter} # filter out empty items from the array...
$files
My idea is to put regex patterns in the $filter variable so I can catch filenames that have datestamps in them such as test120119.txt in the $list variable above.
How can I change my code to allow for regex? I tried some variations of select-string without splitting my $list, but was not fruitful. I also tried changing my -notin to -notmatch but this doesn't work at all of course.
If you want to use regex, I think it would be easier to just fully commit to regex with your $filter array.
$filter = "^test\d{0,6}\.txt","^Fake","^AnotherFile\d{0,6}\.txt" -join '|'
$list = #"
FakeFile.txt
test120119.txt
AnotherFile120119.txt
LastFile.txt
"#
$files = $list -split '\r?\n'
$files | Where {$_ -notmatch $filter}
The thing to keep in mind is remembering to escape special regex characters if you want them treated literally. You can use the [regex]::Escape() method to do this for you but not if you already purposely injected regex characters.
Once you have your regex filter list, you can join each item with a regex or using the | character.
Not all operators recognize regex language. -match and -notmatch are among the few that do. -match and -notmatch are not case-sensitive. If you want to match against case, you should use the -c variants of the operators, namely -cmatch and -cnotmatch.
The regex items can be tweaked to your liking. More requirements would need to be given in order to come up with an exact solution. Here are some examples to consider:
\d{0,6} matches 0 to 6 consecutive digits. 122619 will match successfully, but so will 1226. If you want only 0 or 6 digits to match, you can use (\d{6})?.
^ should be used if you want to start each match at the beginning of the input string. So if you want the regex or to apply from the beginning of the string, you need to include ^ in each item or group items succeeding the initial ^ with () accordingly. ^item1|^item2 will return the same capture group 0 match as ^(item1|item2).
\ escape the literal . characters.
Not using anchor characters like ^ and $ create a lot of flexibility and potentially unwanted results. 'FakeFile' -match 'Fake' returns true but so does 'MyFakeFile' -match 'Fake'. However, 'MyFakeFile' -match 'Fake$' returns false and 'MyFake' -match 'Fake$' returns true.

In PowerShell how do I remove a duplicate item from a split path without changing the order?

I'm trying to remove duplicates from the Windows environment system path. It seems like the only way to do this is to split the path by semicolon and use sort-object with the -unique parameter or pipe to get-unique. However, it changes the sort order completely after that, and I want it to be the same order it was from the beginning, just minus the duplicates.
$RegPath = "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"
$hklm = [Microsoft.Win32.Registry]::LocalMachine
$RegKey = $hklm.OpenSubKey($regPath, $FALSE)
$OldPath = $regKey.GetValue("Path", "", [Microsoft.Win32.RegistryValueOptions]::DoNotExpandEnvironmentNames)
$SplitPath = $OldPath -split ';'
$NoDupesPath = ($SplitPath | Sort-Object | Get-Unique) -join ';'
I want $NoDupesPath to be the same order as $OldPath, minus the duplicates, but that's not happening when using Sort-Object. I also don't want any semicolons to show up at the very beginning or end of $NoDupesPath.
Inspire from this snippet I just threw together.
$s = 'a;b;c;b;b;a;a'
$a = $s -split ';'
$h = [ordered]#{}
# could use any value, or +1 to see count
$a | % {$h[$_] = $h[$_]+1}
"our hash table"
$h
"keys is unique and in order"
$h.Keys
$uniqueSameOrder = $h.Keys -join ';'
"uniqueSameOrder"
$uniqueSameOrder
Indeed, eliminating duplicates with Get-Unique (or, more directly, with Sort-Object -Unique) requires sorting the elements, which contradicts your requirements.
Update: Don Cruickshank's answer offers the simplest solution; conceptually speaking, the solution below is a manual (and therefore unnecessary) re-implementation of Select-Object -Unique.
Kory Gill's helpful answer contains all the ingredients for a solution; here's the full recipe:
$NoDupesPath = $OldPath -split ';' | ForEach-Object `
-Begin { $oht = [ordered] #{} } `
-Process { $oht[$_] = $true } `
-End { $oht.Keys -join ';' }
Due to pipeline use, this won't be the fastest solution, but it's concise and (hopefully) conceptually clear:
At the start of processing (the -Begin block) , $oht = [ordered] #{} creates an empty ordered hashtable (a hashtable whose keys reflect the order in which keys (entries) are created; PSv3+)
For each input path (the -Process block), if ($oht[$_]) { return } is a no-op if the input path at hand is already in the ordered hashtable, thereby skipping duplicates.
$oht[$_] = $true creates a hashtable entry for a path not previously seen; for a duplicate, the existing entry is simply updated, which amounts to a no-op, causing duplicates to be effectively ignored; after all paths have been processed, $oht.Keys therefore contains only unique paths, in input order.
At the end of processing (the -End block), $oht.Keys -join ';' outputs the unique paths - in input order - joined to form a single string with separator ;.
You can use Select-Object -Unique to get a unique collection and keep the order of the first occurrence of each item.
PS> $OldPath = "foo;bar;foo;baz;foo;bar;qux;baz;bar"
PS> $NoDupesPath = ($OldPath -split ';' | Select-Object -Unique) -join ';'
PS> $NoDupesPath
foo;bar;baz;qux
here's a solution that uses Group-Object. [grin] one thing that is not always apparent is that the resulting groups are in the order they are detected. that means you automatically get the original sequence in this situation ...
$OriSequence = 'a;f;c;b;b;f;a;a;c;s;r;v;q'
$SplitSequence = $OriSequence.Split(';')
$UniqueSequence = ($SplitSequence |
Group-Object).Name -join ';'
$OriSequence
$UniqueSequence
output ...
a;f;c;b;b;f;a;a;c;s;r;v;q
a;f;c;b;s;r;v;q

Performing A String Operation in a -replace Expression

I'm trying to make using of String.Substring() to replace every string with its substring from a certain position. I'm having a hard time figuring out the right syntax for this.
$dirs = Get-ChildItem -Recurse $path | Format-Table -AutoSize -HideTableHeaders -Property #{n='Mode';e={$_.Mode};width=50}, #{n='LastWriteTime';e={$_.LastWriteTime};width=50}, #{n='Length';e={$_.Length};width=50}, #{n='Name';e={$_.FullName -replace "(.:.*)", "*($(str($($_.FullName)).Substring(4)))*"}} | Out-String -Width 40960
I'm referring to the following expression
e={$_.FullName -replace "(.:.*)", "*($(str($($_.FullName)).Substring(4)))*"}}
The substring from the 4th character isn't replacing the Full Name of the path.
The paths in question are longer than 4 characters.
The output is just empty for the Full Name when I run the script.
Can someone please help me out with the syntax
EDIT
The unaltered list of strings (as Get-ChildItem recurses) would be
D:\this\is\where\it\starts
D:\this\is\where\it\starts\dir1\file1
D:\this\is\where\it\starts\dir1\file2
D:\this\is\where\it\starts\dir1\file3
D:\this\is\where\it\starts\dir1\dir2\file1
The $_.FullName will therefore take on the value of each of the strings listed above.
Given an input like D:\this\is or D:\this\is\where, then I'm computing the length of this input (including the delimiter \) and then replacing $_.FullName with a substring beginning from the nth position where n is the length of the input.
If input is D:\this\is, then length is 10.
Expected output is
\where\it\starts
\where\it\starts\dir1\file1
\where\it\starts\dir1\file2
\where\it\starts\dir1\file3
\it\starts\dir1\dir2\file1
If you want to remove a particular prefix from a string you can do so like this:
$prefix = 'D:\this\is'
...
$_.FullName -replace ('^' + [regex]::Escape($prefix))
To remove a prefix of a given length you can do something like this:
$len = 4
...
$_.FullName -replace "^.{$len}"
When having trouble, simplify:
This function will do what you are apparently trying to accomplish:
Function Remove-Parent {
param(
[string]$Path,
[string]$Parent)
$len = $Parent.length
$Path.SubString($Len)
}
The following is not the way you likely would use it but does demonstrate that the function returns the expected results:
#'
D:\this\is\where\it\starts
D:\this\is\where\it\starts\dir1\file1
D:\this\is\where\it\starts\dir1\file2
D:\this\is\where\it\starts\dir1\file3
D:\this\is\where\it\starts\dir1\dir2\file1
'# -split "`n" | ForEach-Object { Remove-Parent $_ 'D:\This\Is' }
# Outputs
\where\it\starts
\where\it\starts\dir1\file1
\where\it\starts\dir1\file2
\where\it\starts\dir1\file3
\where\it\starts\dir1\dir2\file1
Just call the function with the current path ($_.fullname) and the "prefix" you are expecting to remove.
The function above is doing this strictly on 'length' but you could easily adapt it to match the actual string with either a string replace or a regex replace.
Function Remove-Parent {
param(
[string]$Path,
[string]$Parent
)
$remove = [regex]::Escape($Parent)
$Path -replace "^$remove"
}
The output was the same as above.