Powershell - How to Transpose an array - powershell

I have an array with data separated by a comma. I need to transpose it so the first part before the comma for each line is joined together by a delimiter as one line and the same for a second part. Example:
AC-2.22,CCI-000012
AC-5.1,CCI-000036
AC-5.3,CCI-001380
I want to have 2 separate variables like so:
variable 1 = AC-2.22; AC-5.1; AC-5.3
Variable 2 = CCI-000012; CCI-000036; CCI-001380
I know this should be simple but I've been staring at code all day and I just want to go eat dinner and goto sleep.
Thanks in advance

Based on the $array of the helpful answer from Santiago Squarzon, you might also use the ConvertFrom-Csv cmdlet to transpose the data using member-access enumeration:
$data = ConvertFrom-Csv $Array -Header var1, var2
$data.var1 -Join ';'
AC-2.22;AC-5.1;AC-5.3
$data.var2 -Join ';'
CCI-000012;CCI-000036;CCI-001380

This is not too hard using .Where method with Split mode:
$array = #(
'AC-2.22,CCI-000012'
'AC-5.1,CCI-000036'
'AC-5.3,CCI-001380'
)
$i = $false
$var1, $var2 = $array.Split(',').Where({ ($i = -not $i) }, 'Split')
$var1 -join '; '
$var2 -join '; '
.Split method works in this case thanks to Member-Access Enumeration, because arrays don't have an .Split method the same is called over each element of the array:
When you use the member-access operator on any object and the specified member exists on that object, the member is invoked. For property members, the operator returns the value of that property. For method members, the operator calls that method on the object.
When you use the member-access operator on a list collection object that doesn't have the specified member, PowerShell automatically enumerates the items in that collection and uses the member-access operator on each enumerated item.

Related

Powershell passing multiple parameters from one script to another [duplicate]

I've seen the # symbol used in PowerShell to initialise arrays.
What exactly does the # symbol denote and where can I read more about it?
In PowerShell V2, # is also the Splat operator.
PS> # First use it to create a hashtable of parameters:
PS> $params = #{path = "c:\temp"; Recurse= $true}
PS> # Then use it to SPLAT the parameters - which is to say to expand a hash table
PS> # into a set of command line parameters.
PS> dir #params
PS> # That was the equivalent of:
PS> dir -Path c:\temp -Recurse:$true
PowerShell will actually treat any comma-separated list as an array:
"server1","server2"
So the # is optional in those cases. However, for associative arrays, the # is required:
#{"Key"="Value";"Key2"="Value2"}
Officially, # is the "array operator." You can read more about it in the documentation that installed along with PowerShell, or in a book like "Windows PowerShell: TFM," which I co-authored.
While the above responses provide most of the answer it is useful--even this late to the question--to provide the full answer, to wit:
Array sub-expression (see about_arrays)
Forces the value to be an array, even if a singleton or a null, e.g. $a = #(ps | where name -like 'foo')
Hash initializer (see about_hash_tables)
Initializes a hash table with key-value pairs, e.g.
$HashArguments = #{ Path = "test.txt"; Destination = "test2.txt"; WhatIf = $true }
Splatting (see about_splatting)
Let's you invoke a cmdlet with parameters from an array or a hash-table rather than the more customary individually enumerated parameters, e.g. using the hash table just above, Copy-Item #HashArguments
Here strings (see about_quoting_rules)
Let's you create strings with easily embedded quotes, typically used for multi-line strings, e.g.:
$data = #"
line one
line two
something "quoted" here
"#
Because this type of question (what does 'x' notation mean in PowerShell?) is so common here on StackOverflow as well as in many reader comments, I put together a lexicon of PowerShell punctuation, just published on Simple-Talk.com. Read all about # as well as % and # and $_ and ? and more at The Complete Guide to PowerShell Punctuation. Attached to the article is this wallchart that gives you everything on a single sheet:
You can also wrap the output of a cmdlet (or pipeline) in #() to ensure that what you get back is an array rather than a single item.
For instance, dir usually returns a list, but depending on the options, it might return a single object. If you are planning on iterating through the results with a foreach-object, you need to make sure you get a list back. Here's a contrived example:
$results = #( dir c:\autoexec.bat)
One more thing... an empty array (like to initialize a variable) is denoted #().
The Splatting Operator
To create an array, we create a variable and assign the array. Arrays are noted by the "#" symbol. Let's take the discussion above and use an array to connect to multiple remote computers:
$strComputers = #("Server1", "Server2", "Server3")<enter>
They are used for arrays and hashes.
PowerShell Tutorial 7: Accumulate, Recall, and Modify Data
Array Literals In PowerShell
I hope this helps to understand it a bit better.
You can store "values" within a key and return that value to do something.
In this case I have just provided #{a="";b="";c="";} and if not in the options i.e "keys" (a, b or c) then don't return a value
$array = #{
a = "test1";
b = "test2";
c = "test3"
}
foreach($elem in $array.GetEnumerator()){
if ($elem.key -eq "a"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "b"){
$key = $elem.key
$value = $elem.value
}
elseif ($elem.key -eq "c"){
$key = $elem.key
$value = $elem.value
}
else{
Write-Host "No other value"
}
Write-Host "Key: " $key "Value: " $value
}

Hashset to keys and values

I have a problem in my script:
here I set a var remotefilehash:
$remotefilehash = $remotefiles -replace '^[a-f0-9]{32}( )', '$0= ' | ConvertFrom-StringData
this creates a hashtable
I then compare those hash's to what I have locally in a hashset
# Create a hash set from the local hashes.
$localHashSet = [System.Collections.Generic.HashSet[string]] $localmd5
# Loop over all remote hashes to find those not among the local hashes.
$diffmd5 = $remotefilehash.Keys.Where({ -not $localHashSet.Contains($_) })
this gets me the hashkeys but I subsequently also need to get the hash values for those keys that are found in the above where ...
this creates a hashtable
Actually, it creates an array of hashtables, because ConvertFrom-StringData turns each pipeline input object into a separate hashtable.
To create a single hashtable, join the input lines to form a single string first - note how -join "`n" is applied to the result of the -replace operation in order to form a single, multi-line string:
$remotefilehash =
($remotefiles -replace '^[a-f0-9]{32}( )', '$0= ' -join "`n") |
ConvertFrom-StringData
To enumerate the key-value pairs instead of just the keys (.Keys) of a hashtable(-like) object, you need to use its .GetEnumerator() method:
$diffmd5 =
$remotefilehash.GetEnumerator().Where({ -not $localHashSet.Contains($_.Key) })
The reason that an enumerator must be requested explicitly is that PowerShell by default considers a hashtable/dictionary a single object that is passed as a whole through the pipeline / passed to enumeration methods such as the .Where() and .ForEach() array methods.
Note that the output of the above command is not itself a hashtable, but a regular, array-like collection (of type [System.Collections.ObjectModel.Collection[psobject]) whose elements happen to be key-value pair objects.
As such, this output is enumerated in the pipeline.

How do I change foreach to for in PowerShell?

I want to print the word exist in a text file and print "match" and "not match". My 1st text file is: xxaavv6J, my 2nd file is 6J6SCa.yB.
If it is match, it return like this:
Match found:
Match found:
Match found:
Match found:
Match found:
Match found: 6J
Match found:
Match found:
Match found:
My expectation is just print match and not match.
$X = Get-Content "C:\Users\2.txt"
$Data = Get-Content "C:\Users\d.txt"
$Split = $Data -split '(..)'
$Y = $X.Substring(0, 6)
$Z = $Y -split '(..)'
foreach ($i in $Z) {
foreach ($j in $Split) {
if ($i -like $j) {
Write-Host ("Match found: {0}" -f $i, $j)
}
}
}
The operation -split '(..)' does not produce the result you think it does. If you take a look at the output of the following command you'll see that you're getting a lot of empty results:
PS C:\> 'xxaavv6J' -split '(..)' | % { "-$_-" }
--
-xx-
--
-aa-
--
-vv-
--
-6J-
--
Those empty values are the additional matches you're getting from $i -like $j.
I'm not quite sure why -split '(..)' gives you any non-empty values in the first place, because I would have expected it to produce 5 empty strings for an input string "xxaavv6J". Apparently it has to do with the grouping parentheses, since -split '..' (without the grouping parentheses) actually does behave as expected. Looks like with the capturing group the captured matches are returned on top of the results of the split operation.
Anyway, to get the behavior you want replace
... -split '(..)'
with
... |
Select-String '..' -AllMatches |
Select-Object -Expand Matches |
Select-Object -Expand Value
You can also replace the nested loop with something like this:
foreach ($i in $Z) {
if (if $Split -contains $i) {
Write-Host "Match found: ${i}"
}
}
A slightly different approach using regex '.Match()' should also do it.
I have added a lot of explaining comments for you:
$Test = Get-Content "C:\Users\2.txt" -Raw # Read as single string. Contains "xxaavv6J"
$Data = (Get-Content "C:\Users\d.txt") -join '' # Read as array and join the lines with an empty string.
# This will remove Newlines. Contains "6J6SCa.yB"
# Split the data and make sure every substring has two characters
# In each substring, the regex special characters need to be Escaped.
# When this is done, we join the substrings together using the pipe symbol.
$Data = ($Data -split '(.{2})' | # split on every two characters
Where-Object { $_.Length -eq 2 } | # don't care about any left over character
ForEach-Object { [Regex]::Escape($_) } ) -join '|' # join with the '|' which is an OR in regular expression
# $Data is now a string to use with regular expression: "6J|6S|Ca|\.y"
# Using '.Match()' works Case-Sensitive. To have it compare Case-Insensitive, we do this:
$Data = '(?i)' + $Data
# See if we can find one or more matches
$regex = [regex]$Data
$match = $regex.Match($Test)
# If we have found at least one match:
if ($match.Groups.Count) {
while ($match.Success) {
# matched text: $match.Value
# match start: $match.Index
# match length: $match.Length
Write-Host ("Match found: {0}" -f $match.Value)
$match = $match.NextMatch()
}
}
else {
Write-Host "Not Found"
}
Result:
Match found: 6J
Further to the excellent Ansgar Wiechers' answer: if you are running (above) Windows PowerShell 4.0 then you could apply the .Where() method described in Kirk Munro's exhaustive article ForEach and Where magic methods:
With the release of Windows PowerShell 4.0, two new “magic” methods
were introduced for collection types that provide a new syntax for
accessing ForEach and Where capabilities in Windows PowerShell.
These methods are aptly named ForEach and Where. I call
these methods “magic” because they are quite magical in how they work
in PowerShell. They don’t show up in Get-Member output, even if you
apply -Force and request -MemberType All. If you roll up your
sleeves and dig in with reflection, you can find them; however, it
requires a broad search because they are private extension methods
implemented on a private class. Yet even though they are not
discoverable without peeking under the covers, they are there when you
need them, they are faster than their older counterparts, and they
include functionality that was not available in their older
counterparts, hence the “magic” feeling they leave you with when you
use them in PowerShell. Unfortunately, these methods remain
undocumented even today, almost a year since they were publicly
released, so many people don’t realize the power that is available in
these methods.
…
The Where method
Where is a method that allows you to filter a collection of objects.
This is very much like the Where-Object cmdlet, but the Where
method is also like Select-Object and Group-Object as well,
includes several additional features that the Where-Object cmdlet
does not natively support by itself. This method provides faster
performance than Where-Object in a simple, elegant command. Like
the ForEach method, any objects that are output by this method are
returned in a generic collection of type
System.Collections.ObjectModel.Collection1[psobject].
There is only one version of this method, which can be described as
follows:
Where(scriptblock expression[, WhereOperatorSelectionMode mode[, int numberToReturn]])
As indicated by the square brackets, the expression script block is
required and the mode enumeration and the numberToReturn integer
argument are optional, so you can invoke this method using 1, 2, or 3
arguments. If you want to use a particular argument, you must provide
all arguments to the left of that argument (i.e. if you want to
provide a value for numberToReturn, you must provide values for
mode and expression as well).
Applied to your case (using the simplest variant Where(scriptblock expression) of the .Where() method):
$X = '6J6SCa.yB' # Get-Content "C:\Users\2.txt"
$Data = 'xxaavv6J' # Get-Content "C:\Users\d.txt"
$Split = ($Data -split '(..)').Where({$_ -ne ''})
$Y = $X.Substring(0, 6)
$Z = ($Y -split '(..)').Where{$_ -ne ''} # without parentheses
For instance, Ansgar's example changes as follows:
PS > ('xxaavv6J' -split '(..)').Where{$_ -ne ''} | % { "-$_-" }
-xx-
-aa-
-vv-
-6J-

Split a string to multiple strings in powershell

I have strings like
$value = "1;#Mohapatra,Mrutyunjaya (ADM) 10;#Sumit Upadhyay(ADM) 11;#Naidu,Ishan(ADM)"
I want to retrieve
"Mohapatra,Mrutyunjaya (ADM)", "Sumit Upadhyay(ADM)", "Naidu,Ishan(ADM)"
from $value.
I have tried $value.Split(";#")[0]. It is returning the first parameter only. But I want all the parameters
Split your string at sequences of \s*\d+;# (optional whitespace, followed by a number, a semicolon, and a hash character), and remove empty elements from the resulting list:
$value -split '\s*\d+;#' | Where-Object { $_ }
Just FYI, if you want to declare each as a variable you can say $a,$b,$c,$d = $Value -Split (";#") and each of $a, $b, $c and $d will retain those values.

What's the proper way to iteratively grab a value from an array when the key is a string?

I'm trying to export some lists from Sharepoint into a CSV file. My goal is to make a single function that is flexible enough to take a List name, identifier for the CSV file, and a list of FieldValues to export, and then produce a CSV file. Here's what I have so far:
function getTableData($_ctx, [string]$_colName)
{
$list = $_ctx.Web.Lists.GetByTitle("$_colName")
$camlQuery = [Microsoft.SharePoint.Client.CamlQuery]::CreateAllItemsQuery(100)
$colItems = $list.GetItems($camlQuery)
$_ctx.Load($colItems)
$_ctx.ExecuteQuery();
return $colItems
}
# More will go into this array, but for now a single entry is sufficient for testing purposes
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[0][2])
{
$cols += #{Name=$col;expression={$_[$col];}}
}
$cols
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop $cols } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
The problem I'm having is in the loop that populates $cols. Basically, each item needs to look like #{Name="Title";expression={$_["Title"];}} in order for select-object in the ForEach to grab the proper fields from the List. Unfortunately $cols ends up being looking like this:
Name Value
---- -----
expression $_[$col];
Name Title
expression $_[$col];
Name GroupCode
Which (somehow) produces a CSV file that looks like this:
"Title","GroupCode"
"LA","LA"
"NY","NY"
"TK","TK"
When the output needs to look like this:
"Title","GroupCode"
"Los Angeles","LA"
"New York","NY"
"Tokyo","TK"
I know the field names are correct - if I hardcode them in like so...
# Grab all items from a list
getListData $ctx $mstLists[0][0] |
%{ select-object -input $_ -prop `
#{Name="Title";expression={$_["Title"];}}, `
#{Name='GroupCode';expression={$_["GroupCode"];}}; } |
Export-Csv -Path ($export_path + '\' + $current_date + '_' + $mstLists[0][1] + '.csv') -Encoding UTF8 -NoTypeInformation
...then I get the desired CSV output. I just can't figure out how to get $_[$col] to instead return $_["Title"]
Not sure what the $mstLists[0][2] meant to refer to, but the following code seems to give what you are after...
$mstLists = #("GroupMst", "Groups", #("Title", "GroupCode"))
$cols = #()
foreach($col in $mstLists[2])
{
$cols += #{Name=$col; Expression = [scriptblock]::Create('$_["{0}"]' -f $col)}
}
$cols
which gives...
Name Value
---- -----
Name Title
Expression $_["Title"]
Name GroupCode
Expression $_["GroupCode"]
In your response to andyb in the comments, you say that each item of the array will follow the format
#("ListName", "CSVFileID", #("Field1", "Field2", "Etc..."))
and that $mstLists[0][2] "refers to the list of fields in the first item in the array."
The problem is that it doesn't refer to the list of fields in the first item of the array, because the first item of the array isn't a list of anything, it's the string GroupMst. When you index into a string, you get the character indicated by the index. Since $mstLists[0] is a string, $mstLists[0][2] returns the third character of that string, which is o.
I suppose you were expecting that the # operator would make the array in the parentheses a single item, which becomes the first element of $mstLists? It doesn't. All the # does is ensure that the expression in the parentheses is evaluated as an array rather than a scalar. So, with $a = ('string'), $a is a string, whereas with $a = #('string'), $a is an array with a single string element.
However, since ("GroupMst", "Groups", #("Title", "GroupCode")) evaluates to an array anyway, putting an # in front of it is redundant. Either way you're still assigning a literal array to the variable. $mstLists is an array of three elements:
Element 0 is the string GroupMst
Element 1 is the string Groups
Element 2 is an array of the strings Title and GroupCode
What you want to do is use , as a unary operator:
$mstLists = , ("GroupMst", "Groups", #("Title", "GroupCode"))
Now $mstLists is an array of a single item whose value is the array described in the bulleted list above, and $mstLists[0][2] evaluates to an array of the strings Title and GroupCode, as you were expecting.
Note that , works as a unary or binary operator that returns an array of the operands. To return a single-element array, you use it as a unary operator in front of that element. If you have multiple literal arrays that you want to assign to mstLists, you only need commas between them, not the one in front:
$mstLists = ("ListName", "CSVFileID", #("Field1", "Field2", "Etc...")), ("ListName2", "CSVFileID2", #("Field1", "Field2", "Etc..."))
That addresses the main problem. That still won't quite give you what you want, because $col won't get interpolated in the Expression scriptblock, so Expression will always be literally $_[$col]. However, in order to figure out how to do what you actually want to do, it would be helpful to see a sample of the contents of $ctx.