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")}
Trying to edit this line of a file ("VoIP.Enabled "1"). I wanna change the 1 to a zero. When I change it with
$dewprefs = Get-Content .\dewrito_prefs.cfg
$dewprefs | Select-String "VoIP.Enabled" | ForEach-Object {$_ -replace "1","0"} | Set-Content .\dewrito_prefs.cfg}`
However when I use this script, it removes 100 other lines, edits the right line, then deletes everything else, just leaving the line I wanted to edit.
Any help on this matter would be highly appreciated
Select-String acts as a filter: that is, the input it is given is only passed out if it matches a pattern.
Therefore, only the line of interest is written to the output file.
Do not use Select-String if all input lines - though possibly modified - should be passed through; use only ForEach-Object, and conditionally modify each input line:
$dewprefs = Get-Content .\dewrito_prefs.cfg
$dewprefs |
ForEach-Object { if ($_ -match 'VoIP\.Enabled') { $_ -replace '1', '0' } else { $_ } } |
Set-Content .\dewrito_prefs.cfg
$_ -match 'VoIP\.Enabled' now does what Select-String did in your original command: it matches only if the input line at hand contains literal VoIP.Enabled (note how the . is escaped as \. to ensure that is treated as a literal in the context of a regular expression).
Note how both branches of the if statement produce output:
$_ -replace '1', '0' outputs the result of replacing all instances of 1 in the input line with 0
$_ simply passes the input line through as-is.
Most likely you could replace the if statement with a single -replace expression, however, and, assuming that the file is small enough to be read as a whole (quite likely, in the case of a configuration file), you can use a variant of Stu's helpful simplification.
Taking full advantage of the fact that -replace supports regexes (regular expressions), the code can update lines based on a key name such as VoIP.Enabled only, without needing to know that key's current value.
$key = 'VoIP.Enabled'
$newValue = '1'
# Construct a regex that matches the entire target line.
$regex = '^\s*' + [regex]::Escape($key) + '\b.*$'
# Build the replacement line.
$modifiedLine = "$key $newValue"
(Get-Content .\dewrito_prefs.cfg) -replace $regex, $modifiedLine | Set-Content .\dewrito_prefs.cfg
Note that writing the output back to the input file only works because the input file was read into memory as a whole, up front, due to enclosing the Get-Content call in (...).
This will work too, with PowerShell v3+, and is a little more succinct:
(Get-Content .\dewrito_prefs.cfg).replace('"VoIP.Enabled "1"', '"VoIP.Enabled "0"') |
Set-Content .\dewrito_prefs.cfg
Your quotes are a little strange (3 double quotes in total?), I've mimicked what you've asked, however.
I have a logfile C:\temp\data.log
It contains the following data:
totalSize = 222,6GB
totalSize = 4,2GB
totalSize = 56,2GB
My goal is to extract the numbers from the file and sum them up including the number after the comma. So far it works if I don't regex the number included with value after comma, and only use the number in front of the comma. The other problem I have is if the file only contains one row like below example, if it only contains one line it splits up the number 222 into three file containing the number 2 in three files. If the above logfile contains 2 lines or more it works and sums up as it should, as long I don't use value with comma.
totalSize = 222,6GB
Here is a bit of the code for the regex to add to end of existing variable $regex included with comma is:
[,](\d{1,})
I haven't included the above regex, as it does not sum up properly then.
The whole script is below:
#Create path variable to store contents grabbed from $log_file
$extracted_strings = "C:\temp\amount.txt"
#Create path variable to read from original file
$log_file = "C:\temp\data.log"
#Read data from file $log_file
Get-Content -Path $log_file | Select-String "(totalSize = )" | out-file $extracted_strings
#Create path variable to write only numbers to file $output_numbers
$output_numbers = "C:\temp\amountresult.log"
#Create path variable to write to file jobblog1
$joblog1_file = "C:\temp\joblog1.txt"
#Create path variable to write to file jobblog2
$joblog2_file = "C:\temp\joblog2.txt"
#Create path variable to write to file jobblog3
$joblog3_file = "C:\temp\joblog3.txt"
#Create path variable to write to file jobblog4
$joblog4_file = "C:\temp\joblog4.txt"
#Create path variable to write to file jobblog5
$joblog5_file = "C:\temp\joblog5.txt"
#Create pattern variable to read with select string
$regex = "[= ](\d{1,})"
select-string -Path $extracted_strings -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value } > $output_numbers
(Get-Content -Path $output_numbers)[0..0] -replace '\s' > $joblog1_file
(Get-Content -Path $output_numbers)[1..1] -replace '\s' > $joblog2_file
(Get-Content -Path $output_numbers)[2..2] -replace '\s' > $joblog3_file
(Get-Content -Path $output_numbers)[3..3] -replace '\s' > $joblog4_file
(Get-Content -Path $output_numbers)[4..4] -replace '\s' > $joblog5_file
$jobdata0 = (Get-Content -Path $joblog1_file)
$jobdata1 = (Get-Content -Path $joblog2_file)
$jobdata2 = (Get-Content -Path $joblog3_file)
$jobdata3 = (Get-Content -Path $joblog4_file)
$jobdata4 = (Get-Content -Path $joblog5_file)
$result = $jobdata0 + $jobdata1 + $jobdata2 + $jobdata3 + $jobdata4
$result
So my questions is:
How can I get this to work if the file C:\temp\data.log only contains one string without dividing that single number into multiple files. It should also work if it contains multiple strings, as it is now it works with multiple strings.
And how can I include the comma values in the calculation?
The result I get if I run this script should be 282, maybe its even possible to shorten the script?
Where $log_file has contents like the example above.
Get-Content $log_file | Where-Object{$_ -match "\d+(,\d+)?"} |
ForEach-Object{[double]($matches[0] -replace ",",".")} |
Measure-Object -Sum |
Select-Object -ExpandProperty sum
Match all of the lines that have numerical values with optional commas. I am assuming they could be optional as I do not know how whole numbers appear. Replace the comma with a period and cast as a double. Using measure object we sum up all the values and expand the result.
Not the only way to do it but it is simple enough to understand what is going on.
You can always wrap the above up in a loop so that you can use it for multiple files. Get-ChildItem "C:temp\" -Filter "job*" | ForEach-Object... etc.
Matt's helpful answer shows a concise and effective solution.
As for what you tried:
As for why a line with a single token such as 222,6 can result in multiple outputs in this command:
select-string -Path $extracted_strings -Pattern $regex -AllMatches |
% { $_.Matches } | % { $_.Value } > $output_numbers
Your regex, [= ](\d{1,}), does not explain the symptom, but just \d{1,} would, because that would capture 222 and 6 separately, due to -AllMatches.
[= ](\d{1,}) probably doesn't do what you want, because [= ] matches a single character that can be either a = or a space; with your sample input, this would only ever match the space before the numbers.
To match characters in sequence, simply place them next to each other: = (\d{1,})
Also note that even though you're enclosing \d{1,} in (...) to create a capture group, your later code doesn't actually use what that capture group matched; use (...) only if you need it for precedence (in which case you can even opt out of subexpression capturing with (?:...)) or if you do have a need to access what the subexpression matched.
That said, you could actually utilize a capture group here (an alternative would be to use a look-behind assertion), which allows you to both match the leading =<space> for robustness and extract only the numeric token of interest (saving you the need to trim whitespace later).
If we simplify \d{1,} to \d+ and append ,\d+ to also match the number after the comma, we get:
= (\d+,\d+)
The [System.Text.RegularExpressions.Match] instances returned by Select-String then allow us to access what the capture group captured, via the .Groups property (the following simplified example also works with multiple input lines):
> 'totalSize = 222,6GB' | Select-String '= (\d+,\d+)' | % { $_.Matches.Groups[1].Value }
222,6
On a side note: your code contains a lot of repetition that could be eliminated with arrays and pipelines; for instance:
$joblog1_file = "C:\temp\joblog1.txt"
$joblog2_file = "C:\temp\joblog2.txt"
$joblog3_file = "C:\temp\joblog3.txt"
$joblog4_file = "C:\temp\joblog4.txt"
$joblog5_file = "C:\temp\joblog5.txt"
could be replaced with (create an array of filenames, using a pipeline):
$joblog_files = 1..5 | % { "C:\temp\joblog$_.txt" }
and
$jobdata0 = (Get-Content -Path $joblog1_file)
$jobdata1 = (Get-Content -Path $joblog2_file)
$jobdata2 = (Get-Content -Path $joblog3_file)
$jobdata3 = (Get-Content -Path $joblog4_file)
$jobdata4 = (Get-Content -Path $joblog5_file)
$result = $jobdata0 + $jobdata1 + $jobdata2 + $jobdata3 + $jobdata4
could then be replaced with (pass the array of filenames to Get-Content):
$result = Get-Content $joblog_files