Powershell Array funnction - arrays

I am running below script and retrieve information from the template and assign permission. Here I would like to get the User as array input my below script is not processing the user as array.
$userObj = [PSCustomObject]((Get-Content -Raw C:\txt\sample.txt) -replace ':','=' | ConvertFrom-StringData)
[array]$userObj.User
for ($i[0]; $userObj.user; $i++) {
Add-MailboxPermission -Identity $userObj.Identity -User $userObj.User -AccessRights FullAccess -InheritanceType All -confirm:$false
}
Here is my text input which is converted as custom object

$userObj.User is a string with comma-separated names. Casting it to an array just gives you an array with one string with comma-separated names, not an array of the names.
[array]$userObj.User ⇒ [ 'auto,auto1' ]
[array]$userObj.User ⇏ [ 'auto', 'auto1' ]
To get an array of the names from the comma-separated string you need to split it:
$userObj.User -split ','
Also, your for loop is broken. Those loops have the following structure:
for (loop variable initialization; condition; loop variable incrementation)
e.g.
for ($i=0; $i -lt 10; $i++)
But you probably don't need a for loop here anyway. If you want to run a command for each element of the array resulting from your split operation use a ForEach-Object instead:
$userObj.User -split ',' | ForEach-Object {
Add-MailboxPermission -Identity $userObj.Identity -User $_ ...
}

Related

Powershell build array with each item a new line [duplicate]

This question already has an answer here:
Powershell: Piping output of pracl command to array
(1 answer)
Closed 1 year ago.
Using Get-ChildItem I have pulled a list of files that meet a criteria, then split a part of the Basename and want to build an array with that part of the name. I can do that successfully, except the array returns on long string. I'd like each part of the array to return on a new line.
Script:
$files = GCI "\\Paths" -Recurse | Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)}
$name = ""
foreach($file in $files){
$file = $file.basename.Split(".")[0]
$array += $file
}
I also tried the following with no luck:
$files = GCI "\\Paths" -Recurse | Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)}
$name = ""
foreach($file in $files){
$file = $file.basename.Split(".")[0]
$array+= $file -split "`n"
}
Current outcome when calling $array:
file01file02file03file04
Desired outcome when calling $array:
file01
file02
file03
file04
The string is returned because $array is not an array. It is typed at assignment and its first assignment is a string. Therefore it keeps appending new values to that string.
You may do the following instead:
$array = foreach($file in $files){
$file.basename.Split(".")[0]
}
When iterated values are output within a foreach statement, that statement output can be captured into a variable. Each value will be an element of an array.
As an aside, the += syntax to add elements to an array is inefficient because a new array is created each time after retrieving all the contents of the current array.
You're already returning an array, so just narrow it down to what you're assigning to your variable.
$files = GCI "\\Paths" -Recurse |
Where-Object {$_.LastWriteTime -ge (Get-Date).Adddays(-22)} |
ForEach-Object -Process {
$_.basename.Split(".")[0]
}
Or, just assign a variable to your foreach loop removing the output to an array.:
$arr = foreach (...)

Powershell for loop throwing "the array index evaluated to null"

For starters, I'm on Fedora 30 using PSCore version 6.2.1. I've encountered this issue in GNOME Terminal and the vscode snap.
I'm on the first challenge of the PSKoans module and I'm stuck when trying to use a for loop. I am given an array of strings, each of which is a collection of strings separated by commas.
$StockData = #(
"Date,Open,High,Low,Close,Volume,Adj Close"
"2012-03-30,32.40,32.41,32.04,32.26,31749400,32.26"
"2012-03-29,32.06,32.19,31.81,32.12,37038500,32.12"
) # The array is much longer than that, but shortened for simplicity's sake
So, my idea is to build a hashtable out of each subsequent string line in the array by using the first string in the array as keys and each following line as a set of values. I'm using -split to split the values apart from within the strings. I want to use a for loop to iterate through the array and pull values, building a hastable in a file to be read later like so:
# Build the array of keys
[array]$keys = $StockData[0] -split ','
# Begin for loop, using $i as int
for ($i = 1, $StockData[$i], $i++) {
# Create a text file for each hastable
New-Item -Name "ht$i.txt" -ItemType File
# Split current string into values
$values = $StockData[$i] -split ','
# Set value int
$valuesInt = 0
foreach ($key in $keys) {
Add-Content -Path "./ht$i.txt" -Value "$key = $values[$valuesInt]"
$valuesInt++
}
}
As I run that, I get the following error:
Index operation failed; the array index evaluated to null.
At /home/user/PSKoans/Foundations/SolutionStockChallenge.ps1:28 char:6
+ for ($i = 1, $stockData[$i], $i++) {
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : NullArrayIndex
I've looked it up and I find all kinds of scenarios in which people get this error message. I didn't really find a solid explanation for the error message the might lead me to an answer.
Reading the error message, it doesn't make sense to me. the array index evaluated to null...but the array index in the first case is $StockData[1] which is a valid index and should return $true and continue with the loop. Am I missing something?
The syntax of your for loop is wrong. The for loop uses semi-colons as separators.
for ($i = 1, $StockData[$i], $i++) {
should be
for ($i = 1; $StockData[$i]; $i++) {
ConvertFrom-Json in PowerShell Core has the coolest switch - AsHashTable. Try this:
$StockData | convertfrom-csv | convertto-json | ConvertFrom-Json -AsHashtable

Out-File output is missing line feeds between lines of data

I am passing in an array of $users.
PS C:\> $users | ft
ID DisplayName AdminID first last Password
---- ----------- ------- ----- ---- --------
Axyz Axyz, Bill NBX_Admin Bill Axyz Secret
The code:
$y = #()
$y = "Create Users process. Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
foreach ($x in $users) {
$y += "User $($x.DisplayName) with NNN of $($x.ID)"
}
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
$y | Out-File "Log.txt"
$y is now an unformatted string array. When I type $y to the screen, it looks great.
If I direct it to Format-Table, it looks great (no headings).
When I output it to a file, and type that file at a Command Prompt (cmd.exe), it looks great.
However, when I pull it up in Notepad, all the output appears on a single line. To be precise, all the data is there, there are no lines of data missing, but there are no CR/LF so all of the data appears on a single line within the file when viewed with Notepad.exe.
As AdminOfThings correctly points out:
While $y = #() assigns an empty array to $y, it doesn't type-constrain that variable, so your very next assignment - $y = "Create Users process ..." - changes the variable type to a string.
Simply using += instead of = in that subsequent assignment would have prevented the problem: $y += "Create Users process ...".
Alternatively, type-constraining the variable creation - [array] $y = #() - i.e., placing a type literal to the left of the variable being assigned (akin to a cast) - would have prevented the problem too.
Subsequent use of += therefore performs simple string concatenation rather than the desired gradual building of an array, with no separators between the "lines" added.[1]
By contrast, had you used an array as intended, both Out-File and Set-Content would automatically insert platform-appropriate newlines[2] between the elements, plus one at the end, on saving (in PSv5+ you can use the -NoNewline switch to opt out).
That said, using += to "extend" an array is inefficient, because what PowerShell must do behind the scenes is create a new array containing the old elements plus the new one(s), given that arrays are fixed-size data structures.
While the performance penalty for use of += to "extend" arrays in a loop only really matters with high iteration counts, it is more concise, convenient and efficient to let PowerShell create arrays for you implicitly, by using your foreach loop as an expression:
# Initialize the array and assign the first element.
# Due to the type constraint ([array]), the RHS string implicitly becomes
# the array's 1st element.
[array] $y = "Create Users process. Run started at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
# Add the strings output by the foreach loop to the array.
# PowerShell implicitly collects foreach output in an array when
# you use it in as an expression.
$y += foreach ($x in $users)
{
"User $($x.displayname) with NNN of $($x.ID)"
}
# Add the final string to the array.
$y += "Completed at $('[{0:MM/dd/yyyy} {0:HH:mm:ss}]' -f (Get-Date))"
# Send the array to a file with Out-File, which separates
# the elements with newlines and adds a trailing one.
# Windows PowerShell:
# Out-File creates UTF-16LE-encoded files.
# Set-Content, which can alternatively be used, creates "ANSI"-encoded files.
# PowerShell Core:
# Both cmdlets create UTF-8-encoded files without BOM.
$y | Out-File "Log.txt"
Note that you can similarly use for, if, do / while / switch statements as expressions.
In all cases, however, as of PowerShell 7.0, these statements can only serve as expressions by themselves; regrettably, using them as the first segment of a pipeline or embedding them in larger expressions does not work - see this GitHub issue.
[1] A simple demonstration of your problem:
# The initialization of $y as #() is overridden by $y = 'first'.
PS> $y = #(); $y = 'first'; $y += 'second'; $y
firstsecond # !! $y contains a single string built with string concatenation
The description of your symptoms is therefore not consistent with your code, as you should have seen a single-line output string in all scenarios (printing directly to the screen / via Format-Table, sending to a file and type-ing that from cmd.exe).
[2] The platform-appropriate newline is reflected in [Environment]::NewLine, and it is "`r`n" (CRLF) on Windows, and just "`n" (LF) on Unix-like platforms (in PowerShell Core).
As using += recreates the array on every iteration I'd suggest to assign the output of a ForEach-Object with it's -Begin, -Process and -End sections to a variable also using a more common approach of the format operator.:
$Log = $users | ForEach-Object -Begin {
"Create Users process. Run started at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
} -Process {
"User {0} with NNN of {1}" -f $_.DisplayName,$_.ID
} -End {
"Completed at [{0:MM/dd/yyyy} {0:HH:mm:ss}]" -f (Get-Date)
}
$Log | Set-Content "Log.txt"

Convert txt to array in powershell

I have a powershell script and a txt database with different number of elements per line.
My txt file is list.txt:
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
My powershell script search.ps1:
#Get file path
$path = Split-Path $script:MyInvocation.MyCommand.Path
$search = #()
Get-Content -LiteralPath "$path\list.txt" | ForEach-Object {
$search += $_
}
So, how to convert each line as a element of array? As this:
$search = #(("10345","doomsday","life","hope","run","stone"),("10346","ride","latest","metal"))
To operate as:
echo $search[0][0]
Here's a concise PSv4+ solution:
$search = (Get-Content -LiteralPath $path\list.txt).ForEach({ , ($_ -split ',') })
The .ForEach() method operates on each line read from the input file by Get-Content.
$_ -split ',' splits each line into an array of strings by separator ,
, (...) wraps this array in an aux. single-item array to ensure that the array is effectively output as a whole, resulting in an array of arrays as the overall output.
Note: Strictly speaking, the .ForEach() method outputs a [System.Collections.ObjectModel.Collection[psobject]] collection rather than a regular PowerShell array ([object[]]), but for all practical purposes the two types act the same.
Note: The .ForEach() method was chosen as a faster alternative to a pipeline with the ForEach-Object (%) cmdlet.
Note that the .ForEach() method requires storing the input collection in memory as a whole first.
A faster and more memory-efficient, though perhaps slightly obscure alternative is to use a switch statement with the -file option:
$search = switch -file $path\list.txt { default { , ($_ -split ',') } }
switch -file processes each line of the specified file.
Since each line should be processed, only a default branch is used, in which the desired splitting is performed.
Use -split. A code snippet you can debug in ISE or VSCode below.
$x1 = #'
"10345","doomsday","life","hope","run","stone"
"10346","ride","latest","metal"
'#
$data = $x1 -split "`r`n"
$data.Count
$data[0] -split ","
$arr = #()
foreach ($row in $data)
{
$arr += ,($row -split ",")
}
"arr"
$arr
"0,3"
$arr[0][3]
"1,3"
$arr[1][3]
So you can split each line in your file returned from Get-Content and add it to your new array which lets you reference how you wanted...
There are other ways you can use your data depending on your needs.
Assuming you do not want each item quoted, you might consider to not using the -Split operator but just evaluating each line with the Invoke-Expression cmdlet or using a more secure [ScriptBlock] for this:
$Search = Get-Content ".\list.txt" | ForEach-Object {,#(&([ScriptBlock]::Create($_)))}

How to use Get-ChildItem with excluding a list of items with an array in Powershell?

I want to use an array for the exclusion:
Remove-Item -Path "$InstallDir\Lang\*" -Exclude "de.txt", "en.txt"
or
Get-ChildItem "$InstallDir\Lang" -EXCLUDE "es.txt", "de.txt"| Remove-Item
These both work fine.
Whereas
Get-ChildItem "$InstallDir\Lang\*" -Exclude "$Language" | remove-item
does not work.
I tried several ways ( e.g. How to use Get-ChildItem with filter array in Powershell? or How to exclude list of items from Get-ChildItem result in powershell?) but I can´t find a solution.
It seems as if $Language can't be interpreted by the command.
This is how $language is built:
[string]$Language = #('"de.txt"')
If ($PackageConfigFile.Language -notlike $Null) {
foreach ($LIP in $PackageConfigFile.Language) {
$Language += ",`n ""$LIP.txt"""
}
}
$language has e.g. the following content
"de.txt",
"en.txt",
"es.txt"
Has anybody an idea?
$Language = #('de.txt')
If ($PackageConfigFile.Language -notlike $Null) {
foreach ($LIP in $PackageConfigFile.Language) {
$Language += "$LIP.txt"
}
}
First:
Construct your $Language argument as an actual PowerShell array; what you attempted creates a multil-line string instead.
Creating that array should be as simple as:
$Language = $PackageConfigFile.Language -replace '$', '.txt'
-replace, with a collection (array) as the LHS, operates on each item in the collection individually; '$', '.txt' effectively appends .txt to the end ($) of each input item, and the resulting modified elements are collected in $Language as an array, of .NET type System.Object[].
Second:
Do not enclose $Language, your array argument, in "...".
Get-ChildItem $InstallDir\Lang\* -Exclude $Language | Remove-Item -WhatIf
If you enclose an array variable in "...", PowerShell converts it to a single string, composed of the array elements concatenated with the value of preference variable $OFS, which defaults to a space; e.g.:
PS> $arr = 'a', 'b', 'c'; "[$arr]"
[a b c]
For readers coming from a UNIX / bash background:
PowerShell variables do NOT need to be double-quoted when they're passed to other commands, whatever they may contain (spaces or other shell metacharacters).
When calling PowerShell-native functionality (cmdlets, functions, scripts), the variable's original type is preserved as-is (the ability to use the .NET Framework's rich type system is the core feature that exemplifies PowerShell's evolutionary quantum leap in the world of shells).
Only use "..." if you explicitly want to pass a string to the target command.

Resources