Variable name is content of another variable - string

I have a list of variables:
$desa = "filtering regex for desa"
$cdo = "different regex for cdo"
etc.
Now, I have a loop:
Foreach ($profilename in ("desa", "cdo")) {
# filter out data from $profilename file where regex is contained in
# variable named after the content of $profilename
}
So, in other words, I need to use a string contained in one of the variables at the top, and the name of that variable is the exact content of the $profilename variable.
Can PowerShell do this?

Might be easier to us a hash table of regexes than separate variables for each one:
$filters = #{
desa = "filtering regex for desa"
cdo = "different regex for cdo"
}
Foreach ($profilename in
( "desa", "cdo")
)
{
(Get-content <profilename file>) -match $filters[$profilename]
}
Just name the keys after your profile names.

As #mjolinor said: hashtables are a better approach for this. However, if for some reason you must expand a "constructed" variable you can do it by using the $ExecutionContext automatic variable:
PS C:\> $a = 'foo'
PS C:\> $b = 'a'
PS C:\> $c = "`$$b"
PS C:\> $c
$a
PS C:\> $ExecutionContext.InvokeCommand.ExpandString($c)
foo
Applied to your code that might look like this:
$desa = "filtering regex for desa"
$cdo = "different regex for cdo"
foreach ($profilename in 'desa','cdo') {
$pattern = $ExecutionContext.InvokeCommand.ExpandString("`$$profilename")
$something | ? { $_ -match $pattern } | ...
}

Related

How to get a hashtable in PowerShell from a multiline string in which keys and values are on different lines?

I have a string of the following format (the number of lines in this string may vary):
$content = #"
key1
value1
key2
value2
key3
value3
"#
I want to put this data in a hashtable.
(In my case, the data in the $content variable is received in the body of the HTTP response from the Invoke-WebRequest cmdlet by Client/Server Protocol in the 'LiveJournal'. But I am interested in the answer to my question for the general case as well.)
I tried to use the cmdlet ConvertFrom-StringData, but it doesn't work for this case:
PS C:\> ConvertFrom-StringData -StringData $content -Delimiter "`n"
ConvertFrom-StringData: Data line 'key1' is not in 'name=value' format.
I wrote the following function:
function toHash($str) {
$arr = $str -split '\r?\n'
$hash = #{}
for ($i = 0; $i -le ($arr.Length - 1); $i += 2) {
$hash[$arr[$i]] = $arr[$i + 1]
}
return $hash
}
This function works well:
PS C:\> toHash($content)
Name Value
---- -----
key3 value3
key2 value2
key1 value1
My question is: is it possible to do the same thing, but shorter or more elegant? Preferably in one-liner (see the definition of this term in the book 'PowerShell 101'). Maybe there is a convenient regular expression for this case?
As commented by #Santiago Squarzon;
The code you already have looks elegant to me, shorter -ne more elegant
For the "Preferably in one-liner", what exactly is definition of a one line:
A single line, meaning a text string with no linefeeds?
Or a single statement meaning a text string with no linefeeds and no semicolons?
Knowing that there are several ways to cheat on this, like assigning a variable in a condition (which is hard to read).
Anyways, a few side notes:
The snippet you show might have a pitfall if you have an odd number of lines and Set-StrictMode -Version Lastest enabled:
Set-StrictMode -Version Latest
toHash "key1`nvalue1`nkey2"
OperationStopped:
Line |
5 | $hash[$arr[$i]] = $arr[$i + 1]
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Index was outside the bounds of the array.
Name Value
---- -----
key1 value1
The variable neme $content, suggests that you reading the data from a file, possibly with Get-Connent. If that is indeed the case you might consider to stream the input (which conserves memory):
$Content -split '\r?\n' | # Get-Content .\Data.txt
Foreach-Object -Begin {
$Hash = #{}
$Key = $Null
} -Process {
if (!$Key) {
$Key = $_
}
else {
$Hash[$Key] = $_
$Key = $Null
}
} -End {
$Hash
}
And if you create use an [ordered] dictionary, you might even put this is a single statement like:
$Content -split '\r?\n' |Foreach-Object { $h = [Ordered]#{} } { if (!$h.count -or $h[-1]) { $h[$_] = $Null } else { $h[$h.Count - 1] = $_ } } { $h }
(Note that -as with the propose in the question- I do not take into account that there might be empty lines in the input data)
See also PowerShell issue: #13817 Enhance hash table syntax

How can I modify this PowerShell script to continue looking for one string after another?

I want this power shell script to search for the occurrence of multiple strings, one after the other, and to append the results in a .txt file.
Currently I am specifying the string that I want to look for, waiting for the script to finish looking for that string and transferring the results into a spreadsheet. This is taking a lot of time as I have to keep specifying the string I want to look for, especially since there are well over 100 that I need to look for.
#ERROR REPORTING ALL
Set-StrictMode -Version latest
$path = "C:\Users\username\Documents\FileName"
$files = Get-Childitem $path -Include *.docx,*.doc,*.ppt, *.xls,
*.xlsx, *.pptx, *.eap -Recurse | Where-Object { !($_.psiscontainer) }
$output =
"C:\Users\username\Documents\FileName\wordfiletry.txt"
$application = New-Object -comobject word.application
$application.visible = $False
$findtext = "First_String"
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
$document = $application.documents.open($file.FullName,$false,$true)
$range = $document.content
$wordFound = $range.find.execute($findText)
if($wordFound)
{
"$file.fullname has found the string called $findText and it is
$wordfound" | Out-File $output -Append
}
}
$document.close()
$application.quit()
}
getStringMatch
This script will look for 'First_String' successfully, I was hoping to be able to specify 'Second_String', 'Third_String' etc rather than replace First_String every time.
As an alternative to the suggestion from #Mathias, you could use Regex to query the document text instead.
Read the context of the document as a string $text = $document.content.text and then use Select-String $findtext -AllMatches to evaluate the matches with $findtext as string representation of a regular expression instead.
Example:
# pipe delimited string as a regular expression
$findtext = "First_String|Second_String|Third_String"
Function getStringMatch
{
# Loop through all *.doc files in the $path directory
Foreach ($file In $files)
{
$document = $application.documents.open($file.FullName,$false,$true)
$text = $document.content.text
$result = $text | Select-String $findtext -AllMatches
if($result)
{
"$file.fullname has found the strings called $($result.Matches.Value) at indexes $($result.Matches.Index)" | Out-File $output -Append
}
}
$document.close()
$application.quit()
}
Note that if you're trying find strings that do have reserved regex character, you'll need to escape them first

Return duplicate names (including partial matches)

Excel guy here that occasionally turns to automating powershell via vba.
I tried to solve https://stackoverflow.com/q/36538022/641067 (now closed) and couldn't get there with my basic powershell knowledge and googlefu alone.
In essence the problem the OP presented is:
There are a list of names in a text file.
Aim is to capture only those names that occurr at least once (so discard unique names, see point (3)).
Names occurring at least once include partial matches, ie Will and William can be considered duplicates and should be retained. Whereas Bill is not a duplicate of William.
I tried various approaches including
Group
Compare-Object see example below
But I was stymied by part (3). I suspect that a loop is required to do this but am curious whether there is a direct Powershellapproach,
Looking forward to hearing from the experts.
what I tried
$a = Get-Content "c:\temp\in.txt"
$b = $a | select -unique
[regex] $a_regex = ‘(?i)(‘ + (($a |foreach {[regex]::escape($_)}) –join “|”) + ‘)’
$c = $b -match $a_regex
Compare-object –referenceobject $c -IncludeEqual $a
Following testscript using a loop would work for the rules you outlined and looks foolproof to me
$t = ('first', 'will', 'william', 'williamlong', 'unique', 'lieve', 'lieven')
$s = $t | sort-object
[String[]]$r = #()
$i = 0;
while ($i -lt $s.Count - 1) {
if ($s[$i+1].StartsWith($s[$i])) {
$r += $s[$i]
$r += $s[$i+1]
}
$i++
}
$r | Sort-Object -Unique
and following testscript using a regex might get you started.
$content = "nomatch`nevenmatch1`nevenmatch12`nunevenmatch1`nunevenmatch12`nunevenmatch123"
$string = (($content.Split("`n") | Sort-Object -Unique) -join "`n")
$regex = [regex] '(?im)^(\w+)(\n\1\w+)+'
$matchdetails = $regex.Match($string)
while ($matchdetails.Success) {
$matchdetails.Value
$matchdetails = $matchdetails.NextMatch()
}

variable and array based on line of text

The file i am trying to read looks like example below
variable, arrElement1, arrElement2, arrElemen3, arrelement[n]...
variable, arrElement1, arrElement2, arrElemen3, arrelement[n]...
.
.
.
variable, arrElement1, arrElement2, arrElemen3, arrelement[n]...
what i am trying to achieve is to read this file and assign "variable" as one element variable
and arrElement's as array of elements
something like that:
:PseudoCode:
foreach (line in text file)
$variable=variable
$array = "arrElement1", "arrElement2", "arrElement3", ....
foreach( $element in $array) {
'do some stuff'
}
thank you in advance
Something like this should work:
Get-Content 'C:\your.txt' | % {
$arr = $_ -split '\s*,\s*'
New-Variable -Name $arr[0] -Value $arr[1..$arr.Length]
}
Edit: According to your pseudo code you meant to keep the first field in one variable and the rest of the fields as an array in the second variable. That's even simpler to achieve:
Get-Content 'C:\your.txt' | % {
$var, $arr = $_ -split '\s*,\s*'
$arr | % {
# do stuff
}
}

PowerShell FINDSTR eqivalent?

What's the DOS FINDSTR equivalent for PowerShell? I need to search a bunch of log files for "ERROR".
Here's the quick answer
Get-ChildItem -Recurse -Include *.log | select-string ERROR
I found it here which has a great indepth answer!
For example, find all instances of "#include" in the c files in this directory and all sub-directories.
gci -r -i *.c | select-string "#include"
gci is an alias for get-childitem
Just to expand on Monroecheeseman's answer. gci is an alias for Get-ChildItem (which is the equivalent to dir or ls), the -r switch does a recursive search and -i means include.
Piping the result of that query to select-string has it read each file and look for lines matching a regular expression (the provided one in this case is ERROR, but it can be any .NET regular expression).
The result will be a collection of match objects, showing the line matching, the file, and and other related information.
if ($entry.EntryType -eq "Error")
Being Object Oriented, you want to test the property in question with one of the standard comparison operators you can find here.
I have a PS script watching logs remotely for me right now - some simple modification should make it work for you.
edit: I suppose I should also add that is a cmdlet built for this already if you don't want to unroll the way I did. Check out:
man Get-EventLog
Get-EventLog -newest 5 -logname System -EntryType Error
On a related note, here's a search that will list all the files containing a particular regex search or string. It could use some improvement so feel free to work on it. Also if someone wanted to encapsulate it in a function that would be welcome.
I'm new here so if this should go in it's own topic just let me know. I figured I'd put it her since this looks mostly related.
# Search in Files Script
# ---- Set these before you begin ----
$FolderToSearch="C:\" # UNC paths are ok, but remember you're mass reading file contents over the network
$Search="Looking For This" # accepts regex format
$IncludeSubfolders=$True #BUG: if this is set $False then $FileIncludeFilter must be "*" or you will always get 0 results
$AllMatches=$False
$FileIncludeFilter="*".split(",") # Restricting to specific file types is faster than excluding everything else
$FileExcludeFilter="*.exe,*.dll,*.wav,*.mp3,*.gif,*.jpg,*.png,*.ghs,*.rar,*.iso,*.zip,*.vmdk,*.dat,*.pst,*.gho".split(",")
# ---- Initialize ----
if ($AllMatches -eq $True) {$SelectParam=#{AllMatches=$True}}
else {$SelectParam=#{List=$True}}
if ($IncludeSubfolders -eq $True) {$RecurseParam=#{Recurse=$True}}
else {$RecurseParam=#{Recurse=$False}}
# ---- Build File List ----
#$Files=Get-Content -Path="$env:userprofile\Desktop\FileList.txt" # For searching a manual list of files
Write-Host "Building file list..." -NoNewline
$Files=Get-ChildItem -Include $FileIncludeFilter -Exclude $FileExcludeFilter -Path $FolderToSearch -ErrorAction silentlycontinue #RecurseParam|Where-Object{-not $_.psIsContainer} # #RecurseParam is basically -Recurse=[$True|$False]
#$Files=$Files|Out-GridView -PassThru -Title 'Select the Files to Search' # Manually choose files to search, requires powershell 3.0
Write-Host "Done"
# ---- Begin Search ----
Write-Host "Searching Files..."
$Files|
Select-String $Search #SelectParam| #The # instead of $ lets me pass the hastable as a list of parameters. #SelectParam is either -List or -AllMatches
Tee-Object -Variable Results|
Select-Object Path
Write-Host "Search Complete"
#$Results|Group-Object path|ForEach-Object{$path=$_.name; $matches=$_.group|%{[string]::join("`t", $_.Matches)}; "$path`t$matches"} # Show results including the matches separated by tabs (useful if using regex search)
<# Other Stuff
#-- Saving and restoring results
$Results|Export-Csv "$env:appdata\SearchResults.txt" # $env:appdata can be replaced with any UNC path, this just seemed like a logical place to default to
$Results=Import-Csv "$env:appdata\SearchResults.txt"
#-- alternate search patterns
$Search="(\d[-|]{0,}){15,19}" #Rough CC Match
#>
This is not the best way to do this:
gci <the_directory_path> -filter *.csv | where { $_.OpenText().ReadToEnd().Contains("|") -eq $true }
This helped me find all csv files which had the | character in them.
PowerShell has basically precluded the need for findstr.exe as the previous answers demonstrate. Any of these answers should work fine.
However, if you actually need to use findstr.exe (as was my case) here is a PowerShell wrapper for it:
Use the -Verbose option to output the findstr command line.
function Find-String
{
[CmdletBinding(DefaultParameterSetName='Path')]
param
(
[Parameter(Mandatory=$true, Position=0)]
[string]
$Pattern,
[Parameter(ParameterSetName='Path', Mandatory=$false, Position=1, ValueFromPipeline=$true)]
[string[]]
$Path,
[Parameter(ParameterSetName='LiteralPath', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[Alias('PSPath')]
[string[]]
$LiteralPath,
[Parameter(Mandatory=$false)]
[switch]
$IgnoreCase,
[Parameter(Mandatory=$false)]
[switch]
$UseLiteral,
[Parameter(Mandatory=$false)]
[switch]
$Recurse,
[Parameter(Mandatory=$false)]
[switch]
$Force,
[Parameter(Mandatory=$false)]
[switch]
$AsCustomObject
)
begin
{
$value = $Pattern.Replace('\', '\\\\').Replace('"', '\"')
$findStrArgs = #(
'/N'
'/O'
#('/R', '/L')[[bool]$UseLiteral]
"/c:$value"
)
if ($IgnoreCase)
{
$findStrArgs += '/I'
}
function GetCmdLine([array]$argList)
{
($argList | foreach { #($_, "`"$_`"")[($_.Trim() -match '\s')] }) -join ' '
}
}
process
{
$PSBoundParameters[$PSCmdlet.ParameterSetName] | foreach {
try
{
$_ | Get-ChildItem -Recurse:$Recurse -Force:$Force -ErrorAction Stop | foreach {
try
{
$file = $_
$argList = $findStrArgs + $file.FullName
Write-Verbose "findstr.exe $(GetCmdLine $argList)"
findstr.exe $argList | foreach {
if (-not $AsCustomObject)
{
return "${file}:$_"
}
$split = $_.Split(':', 3)
[pscustomobject] #{
File = $file
Line = $split[0]
Column = $split[1]
Value = $split[2]
}
}
}
catch
{
Write-Error -ErrorRecord $_
}
}
}
catch
{
Write-Error -ErrorRecord $_
}
}
}
}
FYI:
If you update to Powershell version 7 you can use grep...
I know egrep is in powershell on Azure CLI...
But SS is there!
An old article here: [https://devblogs.microsoft.com/powershell/select-string-and-grep/]

Resources