I do a get-content on a file. Sometimes there are a lot of lines, but it can happen there is only one line (or even 0)
I was doing something like
$csv = (gc $FileIn)
$lastID = $csv[0].Split(' ')[1] #First Line,2nd column
But with only one line, gc return a string and $csv[0] return the first caracter of the string instead of the complete line, and the following Split fail.
Is it possible to do something like :
$lastID = (is_array($csv)?$csv[0]:$csv).Split(' ')[1]
And to do that only if $csv contains at least a line?
Thx for your help,
Tim
There are type operators one can use to test the type of a variable. -is is the one you need. Like so,
$foo = #() # Array
$bar = "zof" # String
$foo -is [array] # Is foo an array?
True # Yes it is
$foo -is [string] # Is foo a string?
False # No it is not
$bar -is [array] # How about bar
False # Nope, not an array
$bar -is [string] # A string then?
True # You betcha!
So something like this could beused
if($csv -is [array]) {
# Stuff for array
} else {
# Stuff for string
}
Instead of doing:
$csv = (gc $FileIn)
you had to
$csv = #(gc $FileIn)
Now the output will always be an array of strings irrespective of the file having one line or not. The rest of the code will just have to treat $csv as an array of strings. This way is better than having to check if the output is an array etc., at the least in this situation.
Related
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"
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 $_ ...
}
So I'm trying to count the words of my text file however when I do get-content the array reads them letter by letter and so it doesn't let me compare them word by word. I hope you guys can help me out!
Clear-Host
#Functions
function Get-Articles (){
foreach($Word in $poem){
if($Articles -contains $Word){
$Counter++
}
}
write-host "The number of Articles in your sentence: $counter"
}
#Variables
$Counter = 0
$poem = $line
$Articles = "a","an","the"
#Logic
$fileExists = Test-Path "text.txt"
if($fileExists) {
$poem = Get-Content "text.txt"
}
else
{
Write-Output "The file SamMcGee does not exist"
exit(0)
}
$poem.Split(" ")
Get-Articles
What your script does, edited down a bit:
$poem = $line # set poem to $null (because $line is undefined)
$Articles = "a","an","the" # $Articles is an array of strings, ok
# check file exists (I skipped, it's fine)
$poem = Get-Content "text.txt" # Load content into $poem,
# also an array of strings, ok
$poem.Split(" ") # Apply .Split(" ") to the array.
# Powershell does that once for each line.
# You don't save it with $xyz =
# so it outputs the words onto the
# pipeline.
# You see them, but they are thrown away.
Get-Articles # Call a function (with no parameters)
function Get-Articles (){
# Poem wasn't passed in as a parameter, so
foreach($Word in $poem){ # Pull poem out of the parent scope.
# Still the original array of lines. unchanged.
# $word will then be _a whole line_.
if($Articles -contains $Word){ # $articles will never contain a whole line
$Counter++
}
}
write-host "The number of Articles in your sentence: $counter" # 0 everytime
}
You probably wanted to do $poem = $poem.Split(" ") to make it an array of words instead of lines.
Or you could have passed $poem words into the function with
function Get-Articles ($poem) {
...
Get-Articles $poem.Split(" ")
And you could make use of the PowerShell pipeline with:
$Articles = "a","an","the"
$poemArticles = (Get-Content "text.txt").Split(" ") | Where {$_ -in $Articles}
$counter = $poemArticles | Measure | Select -Expand Count
write-host "The number of Articles in your sentence: $counter"
TessellatingHeckler's helpful answer explains the problem with your approach well.
Here's a radically simplified version of your command:
$counter = (-split (Get-Content -Raw text.txt) -match '^(a|an|the)$').count
write-host "The number of articles in your sentence: $counter"
The unary form of the -split operator is key here: it splits the input into words by any run of whitespace between words, resulting in an array of individual words.
-match then matches the resulting array of words against a regex that matches words a, an, or the: ^(a|an|the)$.
The result is the filtered subarray of the input array containing only the words of interest, and .count simply returns that subarray's count.
In a directory i do
Get-Item *.txt
When there is one .txt file inside the directory it returns a System.IO.FileSystemInfo
When there are more .txt files it returns a
System.Array
What is the best way to handle this inconsistency? I.e. how do i find out if the function returned an object or an array. Or even better, is there a way that Get-Item always returns an array?
I want to pass the result into an other function. This function expects an array of System.IO.FileSystemInfo objects.
You can force an array to always return:
#(Get-Item *.txt)
Use ForEach-Object
Get-Item *.txt | ForEach-Object {
# Do stuff
$_ # this represents the "current" object
}
One way to insure the result will always be an array is to wrap the expression in #():
#(Get-Item *.txt)
Alternately, if you are holding the result in a variable for use later, just specify an [array]
[array]$list = Get-Item *.txt
This is useful if you want (eg) $list.count which only works with an array. Otherwise you end up with code like:
if ( $result -eq $null) {
$count = 0
} elseif ($list -isnot [array]) {
$count = 1
} else {
$count = $result.count
}
In PowerShell v2, I'm trying to add only unique values to an array. I've tried using an if statement that says, roughly, If (-not $Array -contains 'SomeValue'), then add the value, but this only ever works the first time. I've put a simple code snippet that shows what I'm doing that doesn't work and what I've done as a workaround that does work. Can someone please let me know where my issue is?
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will not work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If (-not $IncorrectArray -contains $Word)
{
$IncorrectArray += $Word
}
}
Write-Host ('IncorrectArray Count: ' + $IncorrectArray.Length)
# This works as expected
$CorrectArray = #()
ForEach ($Word in $Words)
{
If ($CorrectArray -contains $Word)
{
}
Else
{
$CorrectArray += $Word
}
}
Write-Host ('CorrectArray Count: ' + $CorrectArray.Length)
The Result of the first method is an array containing only one value: "Hello". The second Method contains two values: "Hello" & "World". Any help is greatly appreciated.
To fix your code, try -notcontains or at least WRAP your contains-test in parantheses. Atm. your test reads:
If "NOT array"(if array doens't exist) contains word.
This makes no sense. What you want is:
If array does not contain word..
That's written like this:
If (-not ($IncorrectArray -contains $Word))
-notcontains is even better, as #dugas suggested.
The first time around, you evaluate -not against an empty array, which returns true, which evaluates to: ($true -contains 'AnyNonEmptyString') which is true, so it adds to the array. The second time around, you evaluate -not against a non-empty array, which returns false, which evaluates to: ($false -contains 'AnyNonEmptyString') which is false, so it doesn't add to the array.
Try breaking your conditions down to see the problem:
$IncorrectArray = #()
$x = (-not $IncorrectArray) # Returns true
Write-Host "X is $x"
$x -contains 'hello' # Returns true
then add an element to the array:
$IncorrectArray += 'hello'
$x = (-not $IncorrectArray) # Returns false
Write-Host "X is $x"
$x -contains 'hello' # Returns false
See the problem? Your current syntax does not express the logic you desire.
You can use the notcontains operator:
Clear-Host
$Words = #('Hello', 'World', 'Hello')
# This will work
$IncorrectArray = #()
ForEach ($Word in $Words)
{
If ($IncorrectArray -notcontains $Word)
{
$IncorrectArray += $Word
}
}