How to split string variables into two array object variables using powershell? - arrays

I need to split ips and hostnames from the below the input and convert into array object using powershell
I have a input like below
$docu_results = 'existing IPs: "192.168.1.12","","192.168.1.15","192.168.1.16"
existing hostname: "node.example.com","node1.example.com","node2.example.com",""'
my expected output would be like below
$existing_ips = "192.168.1.12","","192.168.1.15","192.168.1.16"
$existing_hosts = "node.example.com","node1.example.com","node2.example.com",""
The above variables existing_ips an d existing_hosts might have empty or null value and its equivalent values in between these variables and i need to save that in a separate variables
Final output would be like below
192.168.1..12 :: node.example.com
192.168.1..15
192.168.1..15 :: node2.example.com
192.168.1..16
missing data:
value is missing for '' -> 'node1.example.com'
value is missing for '192.168.1.16' -> ''

While there may be more concise solutions, for conceptual clarity I'd use a multi-step approach based on -split and -replace:
$docu_results = 'existing IPs: "192.168.1.12","","192.168.1.15","192.168.1.16"
existing hostname: "node.example.com","node1.example.com","node2.example.com",""'
# Extract the two sub-lists from the input string.
$ipList, $hostList =
$docu_results -split 'existing IPs:|existing hostname:' -ne ''
# Split each list into an array of its elements.
$existing_ips = $iplist.Trim() -split ',' -replace '"'
$existing_hosts = $hostList.Trim() -split ',' -replace '"'
Note:
With an array as the LHS, PowerShell comparison operators act as filters (see this answer), which means that -ne '' above filters out any empty tokens from the array that the -split operation outputs; an empty(-string) token results from the fact that one of the separators is at the very start of the input string.

Related

How do I remove characters like "?" from an array powershell

I am trying to validate strings of text taken from PC descriptions in Active Directory.
But I want to remove rogue characters like a single value of "??" from any text before validating any text.
I have this test code as an example. But whenever it hits the random character "??"
It throws this error:
Error:
parsing "??" - Quantifier {x,y} following nothing.
At C:\Users\#####\OneDrive\Workingscripts\testscripts\removeingfromarray.ps1:11 char:5
+ If ($charigmorematch -match $descstr)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], ArgumentException
+ FullyQualifiedErrorId : System.ArgumentException
When all I want to do is remove it from the array!
Any help greatly appreciated.
This is the example code I have.
##Type characters to remove in array.
$charigmorematch = #("?"; "#"; "$")
##array declare
$userdesc = #()
###Where this would be an AD description from AD.
$ADUser = "Offline - ?? - test"
###Split AD Descrip into individual strings
$userdesc = $ADUser.Split("-").Trim()
###Run through them to check for rogue characters to remove
ForEach($descstr in $userdesc)
{
###If match found try and take it out
If ($charigmorematch -match $descstr)
{
###Store match in variable.
$strmatch = ($charigmorematch -match $descstr)
###Get the index of the string
$indexstr = $userdesc.indexof($descstr)
Write=host "Match: $strmatch Index: $indexstr"
###Once found a match of a rogue character then remove from the array!
##But I haven't figured out that code yet.
###Then a command to remove the string from the array with the index number.
###In this case it's likely to be [1] to remove. But the code has to work that out.
}
}
# Sample input.
$ADUser = "Offline - ?? - test"
# Split into tokens by "-", potentially surrounded by spaces,
# and filter out tokens that contain '?', '#', or '$'.
($ADUser -split ' *- *') -notmatch '[?#$]'
The result is the following array of tokens: 'Offline', 'test'
Note that -notmatch, like all comparison operators that (also) operate on strings, acts as a filter with an array as the LHS, as is the case here (-split always returns an an array).
Based on the additional requirements you mentioned in later comments, you're probably looking for something like this (splitting by - or |, trimming of surrounding (...)):
# Sample input
$ADUser = "Notebook PC | (Win 10) | E1234567 - simple ^^ user | Location ?? not # set"
($ADUser -split ' *[-|] *') -notmatch '[?#$]' -replace '^\(|\)$'
This results in the following array of tokens: 'Notebook PC', 'Win 10', 'E1234567', 'simple ^^ user'
Note that unless your input strings have leading or trailing spaces, there is no need for calling .Trim()
As for what you tried:
$charigmorematch -match $descstr
The -match operator:
requires the input string(s) to be the LHS (left-hand side) operand.
requires a regex (regular expression) as the RHS (right-hand side) operand, to formulate a pattern that the input is matched against.
By contrast, your attempted operation:
mistakenly reversed the order of operands ($descstr, as the string in which to look for regex patterns must be the LHS).
mistakenly used an array as the comparison pattern ($charigmorematch), instead of a (single) regex (expressed as a string) that uses a character set ([...]) to specify the characters of interest.

Parsing a String with quoted Fields like a CSV-line in Powershell

I have to parse a variable input-string into a string-array.
The input is a CSV-style comma-separated field-list where each field has its own quoted string.
Because I dont want to write my own full-blown CSV-parser the only working solution I could create till now is this one:
$input = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic"""'
Add-Type -AssemblyName Microsoft.VisualBasic
$enc = [System.Text.Encoding]::UTF8
$bytes = $enc.GetBytes($input)
$stream = [System.IO.MemoryStream]::new($bytes)
$parser = [Microsoft.VisualBasic.FileIO.TextFieldParser]::new($stream)
$parser.Delimiters = ','
$parser.HasFieldsEnclosedInQuotes = $true
$list = $parser.ReadFields()
$list
Output looks like this:
Miller, Steve
Zappa, Frank
Johnson, Earvin "Magic"
Is there any better solution available via another .NET-library for Powersell?
In best case I could avoid this extra bytes-array and stream.
I am also not sure if this VisualBasic-Assembly will be avail on a long term.
Any ideas here?
With some extra precautions for security and to prevent inadvertent string extrapolation, you can combine Invoke-Expression with Write-Output, though note that Invoke-Expression should generally be avoided:
$fieldList = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
# Parse into array.
$fields = (
Invoke-Expression ("Write-Output -- " + ($fieldList -replace '\$', "`0"))
) -replace "`0", '$$'
Note:
-replace '\$', "`0" temporarily replaces literal $ chars. in the input with NUL chars. to prevent accidental (or malicious) string expansion (interpolation); the second -replace operation restores the original $ chars.
See this answer for more information about the regex-based -replace operator.
Prepending Write-Output -- to the resulting string and interpreting the result as a PowerShell command via Invoke-Expression causes Write-Output to parse the remainder of the string as individual arguments and output them as such. -- ensures that any arguments that happen to look like Write-Output's own parameters are not interpreted as such.
If and only if the input string is guaranteed to never contain embedded $ characters, the solution can be simplified to:
$fields = Invoke-Expression "Write-Output -- $fieldList"
Outputting $fields yields the following:
Miller, Steve
Zappa, Frank
Johnson, Earvin "Magic"
Honey, I'm $HOME
Explanation and list of constraints:
The solution relies on making the input string part of a string whose content is a syntactically valid Write-Output call, with the input string serving as the latter's arguments. Invoke-Expression then evaluates this string as if its content had directly been submitted as a command and therefore executes the Write-Output command. Based on how PowerShell parses command arguments, this implies the following constraints:
Supported field separators:
Either: ,-separated (with per-field (unquoted) leading and/or trailing whitespace getting removed, as shown above).
Or: whitespace-separated, using one or more whitespace characters between the fields.
Non-/quoting of embedded fields:
Fields can be quoted:
If single-quoted ('...'), field-internal ' characters must be escaped as ''.
If double-quoted, field-internal " characters must be escaped as either "" or `".
Fields can also be unquoted:
However, such fields mustn't contain any PowerShell argument-mode metacharacters (of these, < > # # are only metacharacters at the start of a token):
<space> ' " ` , ; ( ) { } | & < > # #
Alternative, via ConvertFrom-Csv:
iRon's helpful answer shows a solution based on ConvertFrom-Csv, given that the field list embedded in the input string is comma-separated (,):
On the one hand, it is more limited in that it only supports "..."-quoting of fields and ""-escaping of field-internal ", and doesn't support fields separated by varying amounts of whitespace (only).
On the other hand, it is more flexible, in that it supports any single-character separator between the fields (irrespective of incidental leading/trailing per-field whitespace), which can be specified via the -Delimiter parameter.
What makes the solution awkward is the need to anticipate the max. number of embedded fields and to provide dummy headers (column names) for them (-Header (0..99)) in order to make ConvertFrom-Csv work, which is both fragile and potentially wasteful.
However, a simple trick can bypass this problem: Submit the input string twice, in which case ConvertFrom-Csv treats the fields in the input string as both the column names and as the column values of the one and only output row (object), whose values can then be queried:
$fieldList = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
# Creates the same array as the solution at the top.
$fields = ($fieldList, $fieldList | ConvertFrom-Csv).psobject.Properties.Value
If the list is limited, you might use the parser of the ConvertFrom-Csv cmdlet, like:
$List = '"Miller, Steve", "Zappa, Frank", "Johnson, Earvin ""Magic""", "Honey, I''m $HOME"'
($List | ConvertFrom-Csv -Header (0..99)).PSObject.Properties.Value.Where{ $Null -ne $_ }
Miller, Steve
Zappa, Frank
Johnson, Earvin "Magic"
Honey, I'm $HOME

Powershell to split a string into an array by start and end characters

Given values to extract from a string, where each value is surrounded by a starting character and ending character, what would be the most effective way to achieve this?
eg, to get an array containing values: a b c
$mystring = "=a; =b; =c;"
$CharArray = $mystring.Split("=").Split(";")
There are numerous combinations of -replace, -split, .Split(), and .Replace() that could be used for this task. Here are some examples:
# Since each element is separated by a space, you can replace extraneous characters first
# -split operator alone splits by a space character
# This can have issues if your values contain spaces too
($mystring -replace '=|;').Split(' ')
-split ($mystring -replace '=|;')
# Since splitting creates white space at times, `Trim()` handles that.
# Because you expect an array after splitting, -ne '' will only return non-empty elements
$mystring.Split("=").Split(";").Trim() -ne ''
# Creates a array of of System.Char elements. Take note of the type here as it may not be what you want.
($mystring -replace ' ?=|;').ToCharArray()
# Uses Split(String[], StringSplitOptions) method
($myString -replace ' ?=').Split(';',[StringSplitOptions]::RemoveEmptyEntries)
David, what you have looks good yet here is another way to do it. The -replace method handles the space (" ") and equal sign (=).
$mystring = "=a; =b; =c;"
$CharArray = $mystring -split ";" -replace " |=",""

PowerShell regex to extract SID from filename

I have an array $vhdlist with contents similar to the following filenames:
UVHD-S-1-5-21-8746256374-654813465-374012747-4533.vhdx
UVHD-S-1-5-21-8746256374-654813465-374012747-6175.vhdx
UVHD-S-1-5-21-8746256374-654813465-374012747-8147.vhdx
UVHD-template.vhdx
I want to use a regex and be left with an array containing only SID portion of the filenames.
I am using the following:
$sids = foreach ($file in $vhdlist)
{
[regex]::split($file, '^UVHD-(?:([(\d)(\w)-]+)).vhdx$')
}
There are 2 problems with this: in the resulting array there are 3 blank lines for every SID; and the "template" filename matches (the resulting line in the output is just "template"). How can I get an array of SIDs as the output and not include the "template" line?
You seem to want to filter the list down to those filenames that contain an SID. Filtering is done with Where-Object (where for short); you don't need a loop.
An SID could be described as "S- and then a bunch of digits and dashes" for this simple case. That leaves us with ^UVHD-S-[\d-]*\.vhdx$ for the filename.
In combination we get:
$vhdlist | where { $_ -Match "^UVHD-S-[\d-]*\.vhdx$" }
When you don't really have an array of strings, but actually an array of files, use them directly.
dir C:\some\folder | where { $_.Name -Match "^UVHD-S-[\d-]*\.vhdx$" }
Or, possibly you can even make it as simple as:
dir C:\some\folder\UVHD-S-*.vhdx
EDIT
Extracting the SIDs from a list of strings can be thought as a combined transformation (for each element, extract the SID) and filter (remove non-matches) operation.
PowerShell's ForEach-Object cmdlet (foreach for short) works like map() in other languages. It takes every input element and returns a new value. In effect it transforms a list of input elements into output elements. Together with the -replace operator you can extract SIDs this way.
$vhdlist | foreach { $_ -replace ^(?:UVHD-(S-[\d-]*)\.vhdx|.*)$,"`$1" } | where { $_ -gt "" }
The regex back-reference for .NET languages is $1. The $ is a special character in PowerShell strings, so it needs to be escaped, except when there is no ambiguity. The backtick is the PS escape character. You can escape the $ in the regex as well, but there it's not necessary.
As a final step we use where to remove empty strings (i.e. non-matches). Doing it this way around means we only need to apply the regex once, instead of two times when filtering first and replacing second.
PowerShell operators can also work on lists directly. So the above could even be shortened:
$vhdlist -replace "^UVHD-(S-[\d-]*)\.vhdx$","`$1" | where { $_ -gt "" }
The shorter version only works on lists of actual strings or objects that produce the right thing when .ToString() is called on them.
Regex breakdown:
^ # start-of-string anchor
(?: # begin non-capturing group (either...)
UVHD- # 'UVHD-'
( # begin group 1
S-[\d-]* # 'S-' and however many digits and dashes
) # end group 1
\.vhdx # '.vhdx'
| # ...or...
.* # anything else
) # end non-capturing group
$ # end-of-string anchor

Appending a string to each item of an array

I have an array and when I try to append a string to it the array converts to a single string.
I have the following data in an array:
$Str
451 CAR,-3 ,7 ,10 ,0 ,3 , 20 ,Over: 41
452 DEN «,40.5,0,7,0,14, 21 ,  Cover: 4
And I want to append the week of the game in this instance like this:
$Str = "Week"+$Week+$Str
I get a single string:
Week16101,NYG,42.5 ,3 ,10 ,3 ,3 , 19 ,Over 43 102,PHI,- 1,14,7,0,3, 24 ,  Cover 4 103,
Of course I'd like the append to occur on each row.
Instead of a for loop you could also use the Foreach-Object cmdlet (if you prefer using the pipeline):
$str = "apple","lemon","toast"
$str = $str | ForEach-Object {"Week$_"}
Output:
Weekapple
Weeklemon
Weektoast
Another option for PowerShell v4+
$str = $str.ForEach({ "Week" + $Week + $_ })
Something like this will work for prepending/appending text to each line in an array.
Set array $str:
$str = "apple","lemon","toast"
$str
apple
lemon
toast
Prepend text now:
for ($i=0; $i -lt $Str.Count; $i++) {
$str[$i] = "yogurt" + $str[$i]
}
$str
yogurtapple
yogurtlemon
yogurttoast
This works for prepending/appending static text to each line. If you need to insert a changing variable this may require some modification. I would need to see more code in order to recommend something.
Another solution, which is fast and concise, albeit a bit obscure.
It uses the regex-based -replace operator with regex '^' which matches the position at the start of each input string and therefore effectively prepends the replacement string to each array element (analogously, you could use '$' to append):
# Sample array.
$array = 'one', 'two', 'three'
# Prepend 'Week ' to each element and create a new array.
$newArray = $array -replace '^', 'Week '
$newArray then contains 'Week one', 'Week two', 'Week three'
To show an equivalent foreach solution, which is syntactically simpler than a for solution (but, like the -replace solution above, invariably creates a new array):
[array] $newArray = foreach ($element in $array) { 'Week ' + $element }
Note: The [array] cast is needed to ensure that the result is always an array; without it, if the input array happens to contain just one element, PowerShell would assign the modified copy of that element as-is to $newArray; that is, no array would be created.
As for what you tried:
"Week"+$Week+$Str
Because the LHS of the + operation is a single string, simple string concatenation takes place, which means that the array in $str is stringified, which by default concatenates the (stringified) elements with a space character.
A simplified example:
PS> 'foo: ' + ('bar', 'baz')
foo: bar baz
Solution options:
For per-element operations on an array, you need one of the following:
A loop statement, such as foreach or for.
Michael Timmerman's answer shows a for solution, which - while syntactically more cumbersome than a foreach solution - has the advantage of updating the array in place.
A pipeline that performs per-element processing via the ForEach-Object cmdlet, as shown in Martin Brandl's answer.
An expression that uses the .ForEach() array method, as shown in Patrick Meinecke's answer.
An expression that uses an operator that accepts arrays as its LHS operand and then operates on each element, such as the -replace solution shown above.
Tradeoffs:
Speed:
An operator-based solution is fastest, followed by for / foreach, .ForEach(), and, the slowest option, ForEach-Object.
Memory use:
Only the for option with indexed access to the array elements allows in-place updating of the input array; all other methods create a new array.[1]
[1] Strictly speaking, what .ForEach() returns isn't a .NET array, but a collection of type [System.Collections.ObjectModel.Collection[psobject]], but the difference usually doesn't matter in PowerShell.

Resources