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")}
I have a group of .txt files that contain one or two of the following strings.
"red", "blue", "green", "orange", "purple", .... many more (50+) possibilities in the list.
If it helps, I can tell if the .txt file contains one or two items, but don't know which one/ones they are. The string patterns are always on their own line.
I'd like the script to tell me specifically which one or two string matches (from the master list) it found, and the order in which it found them. (Which one was first)
Since I have a lot of text files to search, I'd like to write the output results to a CSV file as I search.
FILENAME1,first_match,second_match
file1.txt,blue,red
file2.txt,red, blue
file3.txt,orange,
file4.txt,purple,red
file5.txt,purple,
...
I've tried using many individual Select-Strings returning Boolean results to set variables with any matches found, but with the number of possible strings it gets ugly real fast. My search results for this issue has provided me with no new ideas to try. (I'm sure I'm not asking in the correct way)
Do I need to loop through each line of text in each file?
Am I stuck with the process of elimination method by checking for the existence of each search string?
I'm looking for a more elegant approach to this problem. (if one exists)
Not very intuïtive but elegant...
Following switch statement
$regex = "(purple|blue|red)"
Get-ChildItem $env:TEMP\test\*.txt | Foreach-Object{
$result = $_.FullName
switch -Regex -File $_
{
$regex {$result = "$($result),$($matches[1])"}
}
$result
}
returns
C:\Users\Lieven Keersmaekers\AppData\Local\Temp\test\file1.txt,blue,red
C:\Users\Lieven Keersmaekers\AppData\Local\Temp\test\file2.txt,red,blue
where
file1 contains first blue, then red
file2 contains first red, then blue
You can use regex to search to get index (startpos. in line) combine with Select-String which returns linenumber and you're good to go.
Select-String supports an array as value for -Pattern, but unfortunately it stops on a line after first match even when you use -AllMatches (bug?). Because of this we have to search one time per word/pattern. Try:
#List of words. Had to escape them because Select-String doesn't return Matches-objects (with Index/location) for SimpleMatch
$words = "purple","blue","red" | ForEach-Object { [regex]::Escape($_) }
#Can also use a list with word/sentence per line using $words = Get-Content patterns.txt | % { [regex]::Escape($_.Trim()) }
#Get all files to search
Get-ChildItem -Filter "test.txt" -Recurse | Foreach-Object {
#Has to loop words because Select-String -Pattern "blue","red" won't return match for both pattern. It stops on a line after first match
foreach ($word in $words) {
$_ | Select-String -Pattern $word |
#Select the properties we care about
Select-Object Path, Line, Pattern, LineNumber, #{n="Index";e={$_.Matches[0].Index}}
}
} |
#Sort by File (to keep file-matches together), then LineNumber and Index to get the order of matches
Sort-Object Path, LineNumber, Index |
Export-Csv -NoTypeInformation -Path Results.csv -Encoding UTF8
Results.csv
"Path","Line","Pattern","LineNumber","Index"
"C:\Users\frode\Downloads\test.txt","file1.txt,blue,red","blue","3","10"
"C:\Users\frode\Downloads\test.txt","file1.txt,blue,red","red","3","15"
"C:\Users\frode\Downloads\test.txt","file2.txt,red, blue","red","4","10"
"C:\Users\frode\Downloads\test.txt","file2.txt,red, blue","blue","4","15"
"C:\Users\frode\Downloads\test.txt","file4.txt,purple,red","purple","6","10"
"C:\Users\frode\Downloads\test.txt","file4.txt,purple,red","red","6","17"
"C:\Users\frode\Downloads\test.txt","file5.txt,purple,","purple","7","10"
I am replacing multiple strings in a file. The following works, but is it the best way to do it? I'm not sure if doing multiple block expressions is a good way.
(Get-Content $tmpFile1) |
ForEach-Object {$_ -replace 'replaceMe1.*', 'replacedString1'} |
% {$_ -replace 'replaceMe2.*', 'replacedString2'} |
% {$_ -replace 'replaceMe3.*', 'replacedString3'} |
Out-File $tmpFile2
You don't really need to foreach through each replace operations. Those operators can be chained in a single command:
#(Get-Content $tmpFile1) -replace 'replaceMe1.*', 'replacedString1' -replace 'replaceMe2.*', 'replacedString2' -replace 'replaceMe3.*', 'replacedString3' |
Out-File $tmpFile2
I'm going to assume that your patterns and replacements don't really just have a digit on the end that is different, so you might want to execute different code based on which regex actually matched.
If so you can consider using a single regular expression but using a function instead of a replacement string. The only catch is you have to use the regex Replace method instead of the operator.
PS C:\temp> set-content -value #"
replaceMe1 something
replaceMe2 something else
replaceMe3 and another
"# -path t.txt
PS C:\temp> Get-Content t.txt |
ForEach-Object { ([regex]'replaceMe([1-3])(.*)').Replace($_,
{ Param($m)
$head = switch($m.Groups[1]) { 1 {"First"}; 2 {"Second"}; 3 {"Third"} }
$tail = $m.Groups[2]
"Head: $head, Tail: $tail"
})}
Head: First, Tail: something
Head: Second, Tail: something else
Head: Third, Tail: and another
This may be overly complex for what you need today, but it is worth remembering you have the option to use a function.
The -replace operator uses regular expressions, so you can merge your three script blocks into one like this:
Get-Content $tmpFile1 `
| ForEach-Object { $_ -replace 'replaceMe([1-3]).*', 'replacedString$1' } `
| Out-File $tmpFile2
That will search for the literal text 'replaceMe' followed by a '1', '2', or '3' and replace it with 'replacedString' followed by whichever digit was found (the '$1').
Also, note that -replace works like -match, not -like; that is, it works with regular expressions, not wildcards. When you use 'replaceMe1.*' it doesn't mean "the text 'replaceMe1.' followed by zero or more characters" but rather "the text 'replaceMe1' followed by zero or more occurrences ('*') of any character ('.')". The following demonstrates text that will be replaced even though it wouldn't match with wildcards:
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1.*', 'replacedString1'
replacedString1
The wildcard pattern 'replaceMe1.*' would be written in regular expressions as 'replaceMe1\..*', which you'll see produces the expected result (no replacement performed):
PS> 'replaceMe1_some_extra_text_with_no_period' -replace 'replaceMe1\..*', 'replacedString1'
replaceMe1_some_extra_text_with_no_period
You can read more about regular expressions in the .NET Framework here.