Sort-Object -Unique - powershell

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")}

Related

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.

Century10. Underthewire.tech walkthrough

The password for Century10 is the 161st word within the file on the desktop.
NOTE:
- The password will be lowercase no matter how it appears on the screen.
*The question above is where i am facing my challenges. I tried the command below. *
Get-Content C:\Users\Century9\Desktop\Word_File.txt | Select-Object -Index 161
Result was nil. I understand that i need to assign a value to the string as it is now seen as one whole entity. But how do i do it ?
If the token of interest is the 161st word in the file, use the following approach, which splits the file into words irrespective of line breaks[1]:
$pass = (-split (Get-Content -Raw Word_File.txt))[160]
Append .ToLower() if you want to convert the token to all-lowercase.
Note that the above loads the entire file into memory as a single string, using -Raw.
Since array indices are 0-based, it is index [160] that returns the 161st element.
The unary form of the -split operator splits the input into an array of tokens by whitespace.
Note: If you want to split by the stricter definition of what constitutes a word in a regular-expression context, use the following instead:
$pass = ((Get-Content -Raw Word_File.txt) -split '\W+' -ne '')[160]
[1] If your input file contains each word on its own line:
Your solution was on the right track, except that you should pass 160 to Select-Object -Index, because the -Index parameter expects 0-based indices, not 1-based line numbers:
# Extract the 161st line.
$pass = Get-Content Word_File.txt | Select-Object -Index 160
To convert to lowercase:
$pass = (Get-Content Word_File.txt | Select-Object -Index 160).ToLower()
The above will fail if the input file has fewer than 161 lines (with error message You cannot call a method on a null-valued expression).
If you prefer to receive no output quietly instead, use the following (which uses built-in aliases select for Select-Object and foreach for ForEach-Object for brevity):
$pass = Get-Content Word_File.txt | select -Index 160 | foreach ToLower
Try running this:
((Get-Content -Path C:\Users\Century9\Desktop\Word_File.txt -TotalCount 161)[-1]).ToLower()

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

Search Registry and create New-Item

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]

Splitting in Powershell

I want to be able to split some text out of a txtfile:
For example:
Brackets#Release 1.11.6#Path-to-Brackets
Atom#v1.4#Path-to-Atom
I just want to have the "Release 1.11.6" part. I am doing a where-object starts with Brackets but I don't know the full syntax. Here is my code:
"Get-Content -Path thisfile.txt | Where-Object{$_ < IM STUCK HERE > !
You could do this:
((Get-Content thisfile.txt | Where-Object { $_ -match '^Brackets' }) -Split '#')[1]
This uses the -match operator to filter out any lines that don't start with Brackets (the ^ special regex character indicates that what follows must be at the beginning of the line). Then it uses the -Split operator to split those lines on # and then it uses the array index [1] to get the second element of the split (arrays start at 0).
Note that this will throw an error if the split on # doesn't return at least two elements and it assumes that the text you want is always the second of those elements.
$bracketsRelease = Get-Content -path thisfile.txt | foreach-object {
if ( $_ -match 'Brackets#(Release [^#]+)#' )
{
$Matches[1]
}
}
or
(select-string -Path file.txt -Pattern 'Brackets#(Release [^#]+)#').Matches[0].Groups[1].value