From which interface/class does the Count method come from [duplicate] - powershell

I've made a most unfortunate typo costing me quite some precious time:
$errors.Count
This returns "0", even if there are errors, because the variable name should be singular. This does work:
$error.clear() # To ensure a correct repro
Copy-Item asdf fdsa # Will show an error
$error.Count # Will output "1"
However, I now want to know why $errors.Count gave me anything at all, and why it gave me "0". So I went on to do some testing, and got the following results:
$asdf.Count # Will output "0"
$nrOfEinsteinsInSpace.Count # Will output "0"
$a = 0; $a.Count; # Will output "1"
$b = 2; $a.Count; # Will output "1"
$x = 1,2,3; $x.Count; # Will output "3"
And gathering even more data to be able to ask a sensible question here I did:
$true.Count # Will output "1"
$false.Count # Will output "1"
So we have the following different cases:
Array(like) variables, where .Count will output the number of items.
Non-existent variables, where .Count will output "0".
Declared variables, where .Count will output "1".
Built-in variables, where .Count will output "1".
Case 2, 3, and 4 don't make any sense to me (yet). What is going on here? Where is this documented? How does the .Count property work?

Starting in PowerShell V3, the properties Count and Length received very special treatment not related to extended type data (also known as ETS or extended type system).
If the instance has a Count/Length property, then everything continues to work like it did in V1/V2 - the value from the instance is returned.
If the instance does not have a Count/Length property, starting in V3, instead of that being an error, you'll get back 1. And if the instance is $null, you'll get back 0. If you have turned on strict mode, you'll get an error like in V2.
I'll admit this is a bit strange, but it solves a common problem when a cmdlet returns 0, 1, or multiple objects.
Often, you'll iterate through those results via the pipeline or with a foreach statement. For example:
dir nosuchfile* | % { $_ }
foreach ($item in dir nosuchfile*) { $_ }
In the foreach case above, V2 would actually enter the loop if the command didn't return any values. That was changed in V3 to better match peoples expectations, but that also meant that:
foreach ($item in $null) { $_ }
also never enters the loop.
The for statement is another way to iterate through results, e.g.
$results = dir nosuchfile*
for ($i = 0; $i -lt $results.Count; $i++) { $results[$i] }
In this example, it doesn't matter how many objects are in $result, the loop works just fine. Note that in V1/V2, you would have written:
$results = #(dir nosuchfile*)
This ensures $results is an array, but this syntax is ugly and many folks would forget it, hence the change to support Count/Length in a slightly more forgiving way.

To complement Paul's answer, this might be related to extended type data. To quote the relevant part of the documentation:
Extended type data defines additional properties and methods
("members") of object types in Windows PowerShell. You can extend any
type that is supported by Windows PowerShell and use the added
properties and methods in the same way that you use the properties
that are defined on the object types.
And:
There are three sources of extended type data in Windows PowerShell
sessions.
The Types.ps1xml files in the Windows PowerShell installation directory are loaded automatically into every Windows PowerShell session.
If you open that Types.ps1xml file (in $pshome), you'll see this at the beginning of the file:
<Type>
<Name>System.Array</Name>
<Members>
<AliasProperty>
<Name>Count</Name>
<ReferencedMemberName>Length</ReferencedMemberName>
</AliasProperty>
</Members>
</Type>
So my guess is that by providing the ".Count" property, PowerShell assumes this is an array.

Here is how i think it works:
Case 1: In Arrays the .Count Property actually links to the .Length property which shows the number of Items in the Array
Case 2: Non-exitent variables get automatically created by powershell and initialized with value $null
Case 3 / 4: On this one i am not exactly sure why it happens but since neither String nor Int or boolean Objects have a .Count property i could imagine that the Property is inherited by a parent-object.
The behaviour suggests that the variable is treated as array so with 1 Value assigned the output will be 1, without a value the result will be 0.
Edit:
For the sake of completeness here is the Link to the Documentation: Technet, thanks #David Brabant

Related

PowerShell Hashtable - how to select property

I need to get the value of an environment variable from a kubernetes pod. I have my values listed in a hash table.
I call
$hash["service-testurl"].spec.template.spec.containers.env
And it returns a table:
name value
---- -----
ADDR https://test.com
TOKEN 123456789
CERT_PATH public-certs/test
ENVIRONMENT dev
I need to get https://test.com into a variable in my ps1 script, but i'm not sure how to get this value. (consider that for each deployment the url will be different, like abc.com, def.com, ghj.com... so i can't filter by the name test.com)
I was looking for something like $hash["service-testurl"].spec.template.spec.containers.env.name["ADDR"].value
Running $hash["service-testurl"].spec.template.spec.containers.env.PSTypeNames returns
System.Object[]
System.Array
System.Object
To complement your own effective solution:
Even though your display output of $hash["service-testurl"].spec.template.spec.containers.env looks like the representation of a (single) hashtable, the value is actually:
(a) an array, as your diagnostic output with .pstypenames demonstrates,
(b) whose elements are (presumably) [pscustomobject] instances that each have a a .name and a .value property (an easy way to tell is that the display output's column headers are name and value, whereas with hashtables they would be Name and Value).
Leaving aside that the identifier ADDR is a property value rather than a property / key name in your case, you fundamentally cannot use key-based index notation (['ADDR']) on an array - that generally only works on a (single) hashtable (or, more generally, dictionary).[1]
In your case, you need to find the array element whose .name property value is 'ADDR', which then allows you to return its .value property value.
For collections already in memory, the intrinsic .Where() method (as used in your own solution) is a more efficient - and more flexible - alternative to filtering a collection via the Where-Object cmdlet.
It will often not matter in practice, but you can optimize a .Where() call to stop filtering once the first match has been found, if you expect or are only interested in one match:
$hash["service-testurl"].spec.template.spec.containers.env.Where(
{ $_.name -eq 'ADDR' },
'First'
).value
Note that .Where() always returns an array-like collection, even if only a single value is matched - see this answer for details. As such, the .value property access is attempted on that collection, which, however, still works as intended, courtesy of the PowerShell feature known as member-access enumeration.
Note how using (...) around the arguments is now a syntactic necessity.
While with only a single argument - the filter script block ({ ... }) - you can get away with not using (...) - .Where{ $_.name -eq 'ADDR' } as shorthand for .Where({ $_.name -eq 'ADDR' }) - omitting the (...) is problematic for two reasons:
Given that the Where-Object cmdlet can also be referred to as Where (via a built-in alias), the two command forms could be confused, and given that Where-Object allows and is typically used with a space separating the command name from its script-block argument (e.g, 1..3 | Where { $_ -eq 2 }, it is tempting to also try to use a space with the .Where() method, which does not work:
# !! BROKEN, due to space before "{"
(1..3).Where { $_ -eq 2 }
If you add another argument later, you need to remember to use (...)
[1] The fact that key-based index notation does not work with member-access enumeration, i.e. doesn't work on an array of hashtables (only dot notation does, which PowerShell supports for hashtables too) could be considered an inconsistency; e.g. #( #{ foo=1 } ).foo works (dot notation), but #( #{ foo=1 } )['foo'] does not, due to the array wrapper.
However, this inconsistency was declared to be by design - see GitHub issue #17514.
I was able to do it with something similar that #iRon proposed:
$hash["service-testurl"].spec.template.spec.containers.env.where{$_.name -eq 'ADDR'}.value
Thanks!

How to pipe results into output array

After playing around with some powershell script for a while i was wondering if there is a version of this without using c#. It feels like i am missing some information on how to pipe things properly.
$packages = Get-ChildItem "C:\Users\A\Downloads" -Filter "*.nupkg" |
%{ $_.Name }
# Select-String -Pattern "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)" |
# %{ #($_.Matches[0].Groups["packageId"].Value, $_.Matches[0].Groups["version"].Value) }
foreach ($package in $packages){
$match = [System.Text.RegularExpressions.Regex]::Match($package, "(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)")
Write-Host "$($match.Groups["packageId"].Value) - $($match.Groups["version"].Value)"
}
Originally i tried to do this with powershell only and thought that with #(1,2,3) you could create an array.
I ended up bypassing the issue by doing the regex with c# instead of powershell, which works, but i am curious how this would have been done with powershell only.
While there are 4 packages, doing just the powershell version produced 8 lines. So accessing my data like $packages[0][0] to get a package id never worked because the 8 lines were strings while i expected 4 arrays to be returned
Terminology note re without using c#: You mean without direct use of .NET APIs. By contrast, C# is just another .NET-based language that can make use of such APIs, just like PowerShell itself.
Note:
The next section answers the following question: How can I avoid direct calls to .NET APIs for my regex-matching code in favor of using PowerShell-native commands (operators, automatic variables)?
See the bottom section for the Select-String solution that was your true objective; the tl;dr is:
# Note the `, `, which ensures that the array is output *as a single object*
%{ , #($_.Matches[0].Groups["packageId"].Value, $_.Matches[0].Groups["version"].Value) }
The PowerShell-native (near-)equivalent of your code is (note tha the assumption is that $package contains the content of the input file):
# Caveat: -match is case-INSENSITIVE; use -cmatch for case-sensitive matching.
if ($package -match '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)') {
"$($Matches['packageId']) - $($Matches['Version'])"
}
-match, the regular-expression matching operator, is the equivalent of [System.Text.RegularExpressions.Regex]::Match() (which you can shorten to [regex]::Match()) in that it only looks for (at most) one match.
Caveat re case-sensitivity: -match (and its rarely used alias -imatch) is case-insensitive by default, as all PowerShell operators are; for case-sensitive matching, use the c-prefixed variant, -cmatch.
By contrast, .NET APIs are case-sensitive by default; you'd have to pass the [System.Text.RegularExpressions.RegexOptions]::IgnoreCase flag to [regex]::Match() for case-insensitive matching (you may use 'IgnoreCase', which PowerShell auto-converts for you).
As of PowerShell 7.2.x, there is no operator that is the equivalent of the related return-ALL-matches .NET API, [regex]::Matches(). See GitHub issue #7867 for a green-lit but yet-to-be-implemented proposal to introduce one, named -matchall.
However, instead of directly returning an object describing what was (or wasn't) matched, -match returns a Boolean, i.e. $true or $false, to indicate whether matching succeeded.
Only if -match returns $true does information about a match become available, namely via the automatic $Matches variable, which is a hashtable reflecting the matching parts of the input string: entry 0 is always the full match, with optional additional entries reflecting what any capture groups ((...)) captured, either by index, if they're anonymous (starting with 1) or, as in your case, for named capture groups ((?<name>...)) by name.
Syntax note: Given that PowerShell allows use of dot notation (property-access syntax) even with hashtables, the above command could have used $Matches.packageId instead of $Matches['packageId'], for instance, which also works with the numeric (index-based) entries, e.g., $Matches.0 instead of $Matches[0]
Caveat: If an array (enumerable) is used as the LHS operand, -match' behavior changes:
$Matches is not populated.
filtering is performed; that is, instead of returning a Boolean indicating whether matching succeeded, the subarray of matching input strings is returned.
Note that the $Matches hashtable only provides the matched strings, not also metadata such as index and length, as found in [regex]::Match()'s return object, which is of type [System.Text.RegularExpressions.Match].
Select-String solution:
$packages |
Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
ForEach-Object {
"$($_.Matches[0].Groups['packageId'].Value) - $($_.Matches[0].Groups['version'].Value)"
}
Select-String outputs Microsoft.PowerShell.Commands.MatchInfo instances, whose .Matches collection contains one or more [System.Text.RegularExpressions.Match] instances, i.e. instances of the same type as returned by [regex]::Match()
Unless -AllMatches is also passed, .Matches only ever has one entry, hence the use of [0] to target that entry above.
As you can see, working with Select-Object's output objects requires you to ultimately work with the same .NET type as when you call [regex]::Match() directly.
However, no method calls are required, and discovering the properties of the output objects is made easy in PowerShell via the Get-Member cmdlet.
If you want to capture the matches in a jagged array:
$capturedStrings = #(
$packages |
Select-String '(?<packageId>[^\d]+)\.(?<version>[\w\d\.-]+)(?=.nupkg)' |
ForEach-Object {
# Output an array of all capture-group matches,
# *as a single object* (note the `, `)
, $_.Matches[0].Groups.Where({ $_.Name -ne '0' }).Value
}
)
This returns an array of arrays, each element of which is the array of capture-group matches for a given package, so that $capturedStrings[0][0] returns the packageId value for the first package, for instance.
Note:
$_.Matches[0].Groups.Where({ $_.Name -ne '0' }).Value programmatically enumerates all capture-group matches and returns an their .Value property values as an array, using member-access enumeration; note how name '0' must be excluded, as it represents the whole match.
With the capture groups in your specific regex, the above is equivalent to the following, as shown in a commented-out line in your question:
#($_.Matches[0].Groups['packageId'].Value, $_.Matches[0].Groups['version'].Value)
, ..., the unary form of the array-construction operator, is used as a shortcut for outputting the array (symbolized by ... here) as a whole, as a single object. By default, enumeration would occur and the elements would be emitted one by one. , ... is in effect a shortcut to the conceptually clearer Write-Output -NoEnumerate ... - see this answer for an explanation of the technique.
Additionally, #(...), the array subexpression operator is needed in order to ensure that a jagged array (nested array) is returned even in the event that only one array is returned across all $packages.

Powershell output PSCustomObject blocks output hashtable [duplicate]

I'm learning PowerShell and a vast number of articles I read strongly discourages the use of write-host telling me it's "bad practice" and almost always, the output can be displayed in another way.
So, I'm taking the advice and try to avoid use of write-host. One suggestion I found was to use write-output instead. As far as I understand, this puts everything in a pipeline, and the output is executed at the end of the script (?).
However, I have problems outputting what I want. This example demonstrates the issue:
$properties = #{'OSBuild'="910ef01.2.8779";
'OSVersion'="CustomOS 3";
'BIOSSerial'="A5FT-XT2H-5A4B-X9NM"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-output $object
$properties = #{'Site'="SQL site";
'Server'="SQL Server";
'Database'="SQL Database"}
$object = New-Object –TypeName PSObject –Prop $properties
Write-Output $object
This way I get a nice output of the first object displaying the OS data, but the second object containing the SQL data is never displayed. I've tried renaming the variable names, and a bunch of other different stuff, but no luck.
While troubleshooting this problem, I found similar problems with suggestions to just replace write-output with write-host. This gets me very confused. Why are some people strongly discouraging write-host, while other people encourage it?
And how exactly do I output these two objects in a fashionably manner? I do not fully understand the pipeline mechanism of write-output.
Just to clarify: the problem is only a display problem:
When outputting to the console, if the first object is table-formatted (if Format-Table is applied, which happens implicitly in your case), the display columns are locked in based on that first object's properties.
Since your second output object shares no properties with the first one, it contributes nothing to the table display and is therefore effectively invisible.
By contrast, if you programmatically process the script's output - assign it to a variable or send its output through the pipeline to another command - both objects will be there.
See Charlie Joynt's answer for a helpful example of assigning the two output objects to separate variables.
The simplest solution to the display problem is to explicitly format for display each input object individually - see below.
For a given single object inside a script, you can force formatted to-display (to-host) output with Out-Host:
$object | Out-Host # same as: Write-Output $object | Out-Host
Note, however, that this outputs directly and invariably to the console only and the object is then not part of the script's data output (the objects written to the success output stream, the stream with index 1).
In other words: if you try to assign the script's output to a variable or send its output to another command in a pipeline, that object won't be there.
See below for why Out-Host is preferable to Write-Host, and why it's better to avoid Write-Host in most situations.
To apply the technique ad hoc to a given script's output as a whole, so as to make sure you see all output objects, use:
./some-script.ps1 | % { $_ | Out-String } # % is the built-in alias of ForEach-Object
Note that here too you could use Out-Host, but the advantage of using Out-String is that it still allows you to capture the for-display representation in a file, if desired.
Here's a simple helper function (filter) that you can put in your $PROFILE:
# Once defined, you can use: ./some-script.ps1 | Format-Each
Filter Format-Each { $_ | Out-String }
PetSerAl's suggestion - ./some-script.ps1 | Format-List - works in principle too, but it switches the output from the usual table-style output to list-style output, with each property listed on its own line, which may be undesired.
Conversely, however, Format-Each, if an output object is (implicitly) table-formatted, prints a header for each object.
Why Write-Output doesn't help:
Write-Output doesn't help, because it writes to where output objects go by default anyway: the aforementioned success output stream, where data should go.
If the output stream's objets aren't redirected or captured in some form, they are sent to the host by default (typically, the console), where the automatic formatting is applied.
Also, use of Write-Output is rarely necessary, because simply not capturing or redirecting a command or expression implicitly writes to the success stream; another way of putting it:
Write-Output is implied.
Therefore, the following two statements are equivalent:
Write-Output $object # write $object to the success output stream
$object # same; *implicitly* writes $object to the success output stream
Why use of Write-Host is ill-advised, both here and often in general:
Assuming you do know the implications of using Write-Host in general - see below - you could use it for the problem at hand, but Write-Host applies simple .ToString() formatting to its input, which does not give you the nice, multi-line formatting that PowerShell applies by default.
Thus, Out-Host (and Out-String) were used above, because they do apply the same, friendly formatting.
Contrast the following two statements, which print a hash-table ([hashtable]) literal:
# (Optional) use of Write-Output: The friendly, multi-line default formatting is used.
# ... | Out-Host and ... | Out-String would print the same.
PS> Write-Output #{ foo = 1; bar = 'baz' }
Name Value
---- -----
bar baz
foo 1
# Write-Host: The hashtable's *entries* are *individually* stringified
# and the result prints straight to the console.
PS> Write-Host #{ foo = 1; bar = 'baz' }
System.Collections.DictionaryEntry System.Collections.DictionaryEntry
Write-Host did two things here, which resulted in near-useless output:
The [hashtable] instance's entries were enumerated and each entry was individually stringified.
The .ToString() stringification of hash-table entries (key-value pairs) is System.Collections.DictionaryEntry, i.e., simply the type name of the instance.
The primary reasons for avoiding Write-Host in general are:
It outputs directly to the host (console) rather than to PowerShell's success output stream.
As a beginner, you may mistakenly think that Write-Host is for writing results (data), but it isn't.
In bypassing PowerShell's system of streams, Write-Host output cannot be redirected - that is, it can neither be suppressed nor captured (in a file or variable).
That said, starting with PowerShell v5.0, you can now redirect its output via the newly introduced information stream (number 6; e.g., ./some-script.ps1 6>write-host-output.txt); however, that stream is more properly used with the new Write-Information cmdlet.
By contrast, Out-Host output still cannot be redirected.
That leaves just the following legitimate uses of Write-Host:
Creating end-user prompts and colored for-display-only representations:
Your script may have interactive prompts that solicit information from the user; using Write-Host - optionally with coloring via the -ForegroundColor and -BackgroundColor parameters - is appropriate, given that prompt strings should not become part of the script's output and users also provide their input via the host (typically via Read-Host).
Similarly, you can use Write-Host with selective coloring to explicitly create friendlier for-display-only representations.
Quick prototyping: If you want a quick-and-dirty way to write status/diagnostic information directly to the console without interfering with a script's data output.
However, it is generally better to use Write-Verbose and Write-Debug in such cases.
Generally speaking the expectation is for script/functions to return a single "type" of object, often with many instances. For example, Get-Process returns a load of processes, but they all have the same fields. As you'll have seen from the tutorials, etc. you can then pass the output of Get-Process along a pipeline and process the data with subsequent cmdlets.
In your case you are returning two different types of object (i.e. with two different sets of properties). PS outputs the first object, but not the second one (which doesn't match the first) as you discovered. If you were to add extra properties to the first object that match those used in the second one, then you'd see both objects.
Write-Host doesn't care about this sort of stuff. The push-back against using this is mainly to do with (1) it being a lazy way to give feedback about script progress, i.e. use Write-Verbose or Write-Debug instead and (2) it being imperfect when it comes to passing objects along a pipeline, etc.
Clarification on point (2), helpfully raised in the comments to this answer:
Write-Host is not just imperfect with respect to the pipeline /
redirection / output capturing, you simply cannot use it for that in
PSv4 and earlier, and in PSv5+ you have to awkwardly use 6>; also,
Write-Host stringifies with .ToString(), which often produces
unhelpful representations
If your script is really just meant to print data to the console then go ahead and Write-Host.
Alternatively, you can return multiple objects from a script or function. Using return or Write-Output, just return object objects comma-separated. For example:
Test-WriteOutput.ps1
$object1 = [PSCustomObject]#{
OSBuild = "910ef01.2.8779"
OSVersion = "CustomOS 3"
BIOSSerial = "A5FT-XT2H-5A4B-X9NM"
}
$object2 = [PSCustomObject]#{
Site = "SQL site"
Server= "SQL Server"
Database="SQL Database"
}
Write-Output $object1,$object2
The run the script, assigning the output into two variables:
$a,$b = .\Test-WriteOutput.ps1
You'll see that $a is $object1 and $b is $object2.
use write-host, write-output is for pipeline (and by default on console after clear)

Powershell function returns stdout? (Old title: Powershell append to array of arrays appends stdout?)

I've learnt from this thread how to append to an array of arrays. However, I've observed the weirdest behaviour ever. It seems to append stdout too! Suppose I want to append to an array of arrays, but I want to echo debug messages in the loop. Consider the following function.
function WTF {
$Result = #()
$A = 1,2
$B = 11,22
$A,$B | % {
Write-Output "Appending..."
$Result += , $_
}
return $Result
}
Now if you do $Thing = WTF, you might think you get a $Thing that is an array of two arrays: $(1,2) and $(11,22). But that's not the case. If you type $Thing in the terminal, you actually get:
Appending...
Appending...
1
2
11
22
That is, $Thing[0] is the string "Appending...", $Thing[1] is the string "Appending...", $Thing[2] is the array #(1,2), and $Thing[3] is the array #(11,22). Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into, but why does the echoed string "Appending..." get appended to the result??? This is so extremely weird. How do I stop it from doing that?
EDIT
Oh wait, upon further experimenting, the following simpler function might be more revealing:
function WTF {
$Result = #()
1,2 | % {
Write-Output "LOL"
}
return $Result
Now if you do $Thing = WTF, then $Thing becomes an array of length 2, containing the string "LOL" twice. Clearly there is something fundamental about Powershell loops and or Write-Output that I'm not understanding.
ANOTHER EDIT!!!
In fact, the following even simpler function does the same thing:
function WTF {
1,2 | % {
Write-Output "LOL"
}
}
Maybe I just shouldn't be using Write-Output, but should use Write-Information or Write-Debug instead.
PowerShell doesn't have return values, it has a success output stream (the analog of stdout in traditional shells).
The PowerShell pipeline serves as the conduit for this stream, both when capturing command output in a variable and when sending it to another command via |, the pipeline operator
Any statement - including multiple ones, possibly in a loop - inside a function or script can write to that stream, typically implicitly - by not capturing, suppressing, or redirecting output - or explicitly, with Write-Output, although its use is rarely needed - see this answer for more information.
Output is sent instantly to the success output stream, as it is being produced - even before the script or function exits.
return exists for flow control, independently of PowerShell's output behavior; as syntactic sugar you may also use it to write to the output stream; that is, return $Result is syntactic sugar for: $Result; return, with $Result by itself producing implicit output, and return exiting the scope.
To avoid polluting the success output stream - intended for data output only - with status messages, write to one of the other available output streams - see the conceptual about_Redirection help topic.
Write-Verbose is a good choice, because it is silent by default, and can be activated on demand, either via $VerbosePreference = 'Continue', or, on a per-call basis, with the common -Verbose parameter, assuming the script or function is an advanced one.
Write-Host, by contrast, unconditionally prints information to the host (display), and allows control over formatting, notably coloring.
Outputting a collection (array) from a script or function enumerates it. That is, instead of sending the collection itself, as a whole, its elements are sent to PowerShell's pipeline, one by one, a process called streaming.
PowerShell commands generally expect streaming output, and may not behave as expected if you output a collection as a whole.
When you do want to output a collection as a whole (which may sometimes be necessary for performance reasons), wrap them in a transient aux. single-element array, using the unary form of ,, the array constructor operator: , $Result
A conceptually clearer (but less efficient) alternative is to use Write-Output -NoEnumerate
See this answer for more information.
Therefore, the PowerShell idiomatic reformulation of your function is:
function WTF {
# Even though no parameters are declared,
# these two lines are needed to activate support for the -Verbose switch,
# which implicitly make the function an *advanced* one.
# Even without it, you could still control verbose output via
# the $VerbosePreference preference variable.
[CmdletBinding()]
param()
$A = 1,2
$B = 11,22
# Use Write-Verbose for status information.
# It is silent by default, but if you pass -Verbose
# on invocation or set $VerbosePreference = 'Continue', you'll
# see the message.
Write-Verbose 'Appending...'
# Construct an array containing the input arrays as elements
# and output it, which *enumerates* it, meaning that each
# input array is output by itself, as a whole.
# If you need to output the nested array *as a whole*, use
# , ($A, $B)
$A, $B
}
Sample invocation:
PS> $nestedArray = WTF -Verbose
VERBOSE: Appending...
Note:
Only the success output (stream 1) was captured in variable $nestedArray, whereas the verbose output (stream 4) was passsed through to the display.
$nestedArray ends up as an array - even though $A and $B were in effect streamed separately - because PowerShell automatically collects multiple streamed objects in an array, which is always of type [object[]].
A notable pitfall is that if there's only one output object, it is assigned as-is, not wrapped in an array.
To ensure that a command's output is is always an array, even in the case of single-object output:
You can enclose the command in #(...), the array-subexpression operator
$txtFiles = #(Get-ChildItem *.txt)
In the case of a variable assignment, you can also use a type constraint with [array] (effectively the same as [object[]]):
[array] $txtFiles = Get-ChildItem *.txt
However, note that if a given single output object itself is a collection (which, as discussed, is unusual), no extra array wrapper is created by the commands above, and if that collection is of a type other than an array, it will be converted to a regular [object[]] array.
Additionally, if #(...) is applied to a strongly typed array (e.g., [int[]] (1, 2), it is in effect enumerated and rebuilt as an [object[]] array; by contrast, the [array] type constraint (cast) preserves such an array as-is.
As for your specific observations and questions:
I've learnt from this thread how to append to an array of arrays
While using += in order to incrementally build an array is convenient, it is also inefficient, because a new array must be constructed every time - see this answer for how to construct arrays efficiently, and this answer for how to use an efficiently extensible list type as an alternative.
Nevermind that they seem to be in a weird order, which is a whole other can of worms I don't want to get into
Output to the pipeline - whether implicit or explicit with Write-Output is instantly sent to the success output stream.
Thus, your Write-Output output came first, given that you didn't output the $Result array until later, via return.
How do I stop it from doing that?
As discussed, don't use Write-Output for status messages.

IF there are folders older than 30 days

The script below finds folders older than 30 days. I just want to add a simple IF statement to say "There are not folders older than 30 days" if there are not any. But I am not sure how to do that
Thank you
$Test = Get-ChildItem "\\Server\XFER\Cory" -Directory |
Sort LastWriteTime -Descending |
Where-Object {($_.LastWriteTime -lt (Get-Date).AddDays(-30))} |
Select-Object Name, LastWriteTime
Your question boils down to this:
In PowerShell, how can I determine if a command or expression produced any output?
In almost all cases, the following is sufficient, as Ash suggests:
$Test = ... # ... represents any command or expression
if ($null -eq $Test) { 'no output' }
If you know that the command or expression - when it does produce output - only ever emits non-numeric and non-Boolean objects, you can simplify to the following, as Santiago Squarzon suggests, relying on PowerShell's implicit to-Boolean coercion logic, summarized in the bottom section of this answer:
$Test = ... # ... represents any command or expression
if (-not $Test) { 'no output' }
If you're dealing with an (unusual) command that outputs a collection (array) as a single object (as opposed to enumerating the collection and outputting each element separately, which is the normal pipeline behavior) and you want to treat an empty collection object as lack of output too:
$Test = ... # ... represents any command or expression
# Caveat: Due to a bug, only works robustly with
# Set-StrictMode turned off (the default)
# or Set-StrictMode -Version 1 (not higher).
if ($Test.Count -eq 0) { 'no output' }
Note that this works even with $null output and a scalar output object (a single, non-collection object). In the interest of unified treatment of scalars and collections (arrays), PowerShell adds a .Count property even to scalars that themselves do not have it, so that scalars can be treated as if they were a single-element collection, also with respect to indexing; e.g. (42).Count is 1, and (42)[0] is 42; however, note that $null.Count is 0. Such PowerShell engine-supplied type members are called intrinsic members.
Caveat: Due to a long-standing bug - reported in GitHub issue #2798 and still present as of PowerShell 7.2 - accessing the intrinsic .Count property on objects that don't natively have it causes a statement-terminating error if Set-StrictMode -Version 2 or higher is in effect.
However, the tests above do not allow you to distinguish between no output at all and a (single) $null value, which requires the following approach - though do note that actual $null output is unusual in PowerShell:[1]
$Test = ... # ... represents any command or expression
if ($null -eq $Test -and #($Test).Length -eq 0) { 'no output' }
This obscure test is necessary, because no output in PowerShell is represented by the [System.Management.Automation.Internal.AutomationNull]::Value] singleton, which behaves like $null in expression contexts, but not in enumeration contexts such as in a pipeline, including with #(...), the array-subexpression operator, which returns an empty array for [System.Management.Automation.Internal.AutomationNull]::Value] (element count 0) and a single-element array for $null.
While making the distinction between $null and [System.Management.Automation.Internal.AutomationNull]::Value often isn't necessary, there are definitely cases when it is, given that their enumeration behavior differs. Being able to distinguish via a simple test such as $Test -is [AutomationNull] is the subject of GitHub proposal #13465.
[1] Returning $null from PowerShell commands (cmdlets, scripts, functions) is best avoided; instead, simply omit output commands. However, .NET API methods may still return $null and object properties may contain $null (even [string]-typed ones, and even in PowerShell class definitions - see GitHub issue #7294).