PowerShell $_ syntax - powershell

In this answer the author proposed the following snippet:
dir -Path C:\FolderName -Filter *.fileExtension -Recurse | %{$_.FullName}
I can understand the majority of it, but I'm unable to search documentation for the last part. The output of the search is piped | and used in %{} and as $_.
I have experimented around it, %{} is a for-each statement I believe, bing search was not effective. $_ is also somewhat magic: it is a variable, with no name and thus immediately consumed? I don't care much for the .FullName, that part I sorted out. Again, bing search was not effective, nor searching for those char sequences in PowerShell docs.
Can anybody explain it to me?

%{} is not "a thing" - it's two things: % and {}
% is an alias for the ForEach-Object cmdlet:
PS ~> Get-Alias '%'
CommandType Name Version Source
----------- ---- ------- ------
Alias % -> ForEach-Object
... so it resolves to:
... |ForEach-Object { $_.FullName }
ForEach-Object is basically PowerShell's map function - it takes input via the pipeline and applies the operation described in the {} block to each one of them.
$_ is an automatic reference to the current pipeline input item being processed
You can think of it a bit like a foreach($thing in $collection){} loop:
1..10 |ForEach-Object { $_ * 10 }
# produces the same output as
foreach($n in 1..10){
$n * 10
}
Except we can now stick our loop in the middle of a pipeline and have it produce output for immediate consumption:
1..10 |ForEach-Object { $_ * 10 } |Do-SomethingElse
ForEach-Object is not the only thing that makes use of the $_ automatic variable in PowerShell - it's also used for pipeline-binding expressions:
mkdir NewDirectory |cd -Path { $_.FullName }
... as well as property expressions, a type of dynamic property definition supported by a number of cmdlets like Sort-Object:
1..10 |Sort-Object { -$_ } # sort in descending order without specifying -Descending
... Group-Object:
1..10 |Group-Object { $_ % 3 } # group terms by modulo congruence
... and Select-Object:
1..10 |Select-Object #{Name='TimesTen';Expression={$_ * 10}} # Create synthetic properties based on dynamic value calculation over input

To complement Mathias' answer, which explains the specific constructs well, with how you could / couldn't have discovered this information yourself, using PowerShell's own help system:
Relevant help topics and use of the help system:
Note: To get an overview of all aspects of PowerShell's help system, simply run help.
% is a built-in alias for the ForEach-Object cmdlet:
Use Get-Help ForEach-Object to view the help topic in the terminal.
If no local topics are found, you must download them via the Update-Help cmdlet.
Tips:
Add the -Online switch to open the (potentially more current) online version of the topic in your browser.
You can bootstrap your use of Get-Help with Get-Help Get-Help (or even help help):
Cmdlet-specific help comes in detail levels: terse (default, shows the syntax and overview description only), -Detailed (includes parameter descriptions and example commands) and -Full (additionally includes technical parameter information and extended notes).
-Examples can be used to show example commands only.
With keyword-based search (see below), you can limit results to topics of a certain category with the -Category parameter.
For convenience, you can also use the built-in help function, which wraps Get-Help calls with display paging (simply put: by piping the output to the more utility) and defaults to detail level -Full.
{...} is a script block literal, a block of arbitrary PowerShell code that can be invoked on demand:
help about_Script_Blocks shows the topic locally; the about_ prefix indicates that the topic is a conceptual help topic (rather than one covering a specific command); when you use Get-Help to search for a keyword (see below), you can (somewhat obscurely) limit the results to conceptual topics with -Category HelpFile.
Note: As of this writing, about_ topics can not yet be directly viewed online by adding -Online - see GitHub issue #13550 - but it's easy to google them by name.
$_ is a variable, as the $ sigil followed by an identifier implies, and is more specifically an automatic (built-in) variable:
help about_Variables covers variables in general.
help about_Automatic_Variables covers the automatic ones.
How the above can / cannot be discovered based on symbols and aliases alone:
Doing a web search for symbols is notoriously unhelpful.
As an aside: Running distinct syntax constructs such as % and { ... } together without whitespace between them (e.g. %{$_.FullName}) constitutes an additional barrier, and should therefore be avoided.
Narrowing your search by using only PowerShell's help system helps, but only to a limited degree:
%
Because Get-Help is aware of aliases, help % actually works fine and directly shows ForEach-Object's help topic.
help % -Examples shows example commands that include the use of script blocks and the automatic $_ variable.
Even though Get-Help supports keyword-based search, searching for symbol-based terms {} and $_ directly isn't helpful, because even when limiting the search to conceptual (about_-prefixed topics) with -Category HelpFile, there are either too many hits (help '$_' -Category HelpFile) or the relevant topic doesn't show at all (help '{}' -Category HelpFile)
$_ can be discovered indirectly, IF you already know that it is an instance of a variable:
help variables -Category HelpFile happens to take you directly to the relevant (local) about_Automatic_Variables topic,
whereas help variable -Category HelpFile lists the following matching topics about_Variable_Provider, ``, about_Automatic_Variables, about_Preference_Variables, about_Remote_Variables, about_Variables, and about_Environment_Variables
Note: Thanks to PowerShell's pervasive support for wildcard expressions, you could have performed the search also as follows: help about*variable* - be sure to enclose both sides of the search term in *.
$_ can be discovered indirectly, IF you already know that it is an instance of a script (code) block:
help about_*block* takes you directly to the relevant (local) about_Script_Blocks topic.
Potential future improvements:
It would be a great improvement if PowerShell's help system supported focused symbol-based searches.
Similarly, the ability to directly look up operators, such as -match, the regular-expression matching operator, would be helpful:
GitHub issue #11339 proposes just that.
On a related note, GitHub issue #11338 proposes adding the ability to look up documentation for .NET types (online).
This answer contains custom functions Show-OperatorHelp and Show-TypeHelp, which fill that gap for now (also available as Gists).

Related

How to sort the output of winget list by column in powershell?

I'm not getting the expected output when trying to sort the output from winget list in powershell. The Id column is not sorted.
# winget list | Sort-Object -Property Id
ScreenToGif NickeManarin.ScreenToGif 2.37.1 winget
Microsoft Visual C++ 2015-2019 Redist… Microsoft.VCRedist.2015+.x64 14.28.29325.2 14.34.318… winget
paint.net {28718A56-50EF-4867-B4C8-0860228B5EC9} 4.3.8
Python 3.10.0 (64-bit) {21b42743-c8f9-49d7-b8b6-b5855317c7ed} 3.10.150.0
Microsoft Support and Recovery Assist… 0527a644a4ddd31d 17.0.7018.4
-----------------------------------------------------------------------------------------------------------------------
Name Id Version Available Source
Paint 3D Microsoft.MSPaint_8wekyb3d8bbwe 6.2009.30067.0
Microsoft .NET SDK 6.0.402 (x64) Microsoft.DotNet.SDK.6 6.0.402 winget
3D Viewer Microsoft.Microsoft3DViewer_8wekyb3d8… 7.2010.15012.0
Microsoft Sticky Notes Microsoft.MicrosoftStickyNotes_8wekyb… 3.8.8.0
Q: How can I sort the output of winget list by the Id column in powershell?
I would like to see a powershell solution similar to the Bash sort -k <column-number>, to sort on any column. I fail to see why this obvious function is not available in powershell?
It outputs text, not an object with properties like "Id". This program's output isn't very smart. It looks like it outputs some special characters as well like … (U+2026 HORIZONTAL ELLIPSIS). The first thing that occurs to me is to cut off the first 39 characters and then sort it by column 40 onward, where Id starts. That should be like sort -k in unix. I believe a powershell version of winget is coming in the future. Replacing non-ascii with spaces and skipping the first 4 lines.
# or -creplace '\P{IsBasicLatin}'
(winget list) -replace '[^ -~]',' ' | select-object -skip 4 |
sort-object { $_.substring(39) }
Python 3.10.0 (64-bit) {21b42743-c8f9-49d7-b8b6-b5855317c7ed} 3.10.150.0
paint.net {28718A56-50EF-4867-B4C8-0860228B5EC9} 4.3.8
Microsoft Support and Recovery Assist 0527a644a4ddd31d 17.0.7018.4
Name Id Version Available Source
ScreenToGif NickeManarin.ScreenToGif 2.37.1 winget
Microsoft .NET SDK 6.0.402 (x64) Microsoft.DotNet.SDK.6 6.0.402 winget
3D Viewer Microsoft.Microsoft3DViewer_8wekyb3d8 7.2010.15012.0
Microsoft Sticky Notes Microsoft.MicrosoftStickyNotes_8wekyb 3.8.8.0
Paint 3D Microsoft.MSPaint_8wekyb3d8bbwe 6.2009.30067.0
Microsoft Visual C++ 2015-2019 Redist Microsoft.VCRedist.2015+.x64 14.28.29325.2 14.34.318 winget
Trying out the Cobalt module that uses Crescendo to parse Winget. There's no name property, and version is just a string (apparently these things are more of a challenge). There's a lot of guid's at the top.
install-module cobalt -scope currentuser
get-wingetpackage | sort id
ID Version Available Source
-- ------- --------- ------
{04F3299A-F322-45A6-8281-046777B9C736} 21.0.3
{0E8670B8-3965-4930-ADA6-570348B67153} 11.0.2100.60
{0EDB70B6-EEA7-413B-BBC4-89E2CD36EFDE} 11.5.18
#...
7zip.7zip 21.07 22.01 winget
Acrylic Suite
Acrylic Wi-Fi Home
To complement the existing, helpful answers:
I would like to see a powershell solution similar to the Bash sort -k <column-number>, to sort on any column.
I fail to see why this obvious function is not available in powershell?
The sort utility does not sort by columns with -k (--key); it sorts by fields, with any non-empty run of whitespace acting as the field separator by default.
Given that a field-based solution isn't possible here - the fields have fixed width, so there's no separator (-t, --field-separator) you can specify - you'd have to use -k 1.40 to achieve column-based sorting, which is (a) far from obvious and (b) is the equivalent of passing { $_.substring(39) } to Sort-Object's -Property parameter, as in js2010's answer.
winget list | Sort-Object -Property Id
While -Property Id would indeed be wonderful if it worked, it cannot be expected to work with the text representations that the external program winget.exe outputs: what PowerShell then sees in the pipeline are strings, about whose content nothing is known, so they can't be expected to have an .Id property.
Should the functionality provided by winget.exe ever be exposed in a PowerShell-native way,[1] i.e. via cmdlets, they would indeed produce (non-string) objects with properties that would allow you to use Sort-Object -Property Id.
Dealing with winget.exe directly comes with the following challenges, owing to its nonstandard behavior (see also the bottom section):
It doesn't respect the current console's code page and instead invariably outputs UTF-8-encoded output.
To compensate for that, [Console]::OutputEncoding must (temporarily) be set to [System.Text.UTF8Encoding]::new()
It doesn't modify its progress-display behavior based on whether its stdout stream is connected directly to a console (terminal) or not; that is, it should suppress progress information when its output is being captured or redirected, but it currently isn't.
To compensate for that, the initial output lines that are the result of winget.exe's progress display must be filtered out.
Thus, an adapted version of js2010's answer would look like this:
# Make PowerShell interpret winget.exe's output as UTF-8.
# You may want to restore the original [Console]::OutputEncoding afterwards.
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
(winget list) -match '^\p{L}' | # filter out progress-display and header-separator lines
Select-Object -Skip 1 | # skip the header line
Sort-Object { $_.Substring(39) }
Parsing winget.exe list output into objects:
The textual output from winget.exe list reveals an inherent limitation that PowerShell-native commands with their separation of data output from its presentation do not suffer from: truncating property values with … represents omission of information that cannot be recovered.
Thus, the following solution is limited by whatever information is present in winget.exe's textual output.
Assuming that helper function ConvertFrom-FixedColumnTable (source code below) is already defined, you can use it to transform the fixed-with-column textual output into objects ([pscustomobject] instances) whose properties correspond to the table's columns, which then allows you to sort by properties (columns), and generally enables OOP processing of the output.
[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
(winget list) -match '^(\p{L}|-)' | # filter out progress-display lines
ConvertFrom-FixedColumnTable | # parse output into objects
Sort-Object Id | # sort by the ID property (column)
Format-Table # display the objects in tabular format
ConvertFrom-FixedColumnTable source code:
# Note:
# * Accepts input only via the pipeline, either line by line,
# or as a single, multi-line string.
# * The input is assumed to have a header line whose column names
# mark the start of each field
# * Column names are assumed to be *single words* (must not contain spaces).
# * The header line is assumed to be followed by a separator line
# (its format doesn't matter).
function ConvertFrom-FixedColumnTable {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)] [string] $InputObject
)
begin {
Set-StrictMode -Version 1
$lineNdx = 0
}
process {
$lines =
if ($InputObject.Contains("`n")) { $InputObject.TrimEnd("`r", "`n") -split '\r?\n' }
else { $InputObject }
foreach ($line in $lines) {
++$lineNdx
if ($lineNdx -eq 1) {
# header line
$headerLine = $line
}
elseif ($lineNdx -eq 2) {
# separator line
# Get the indices where the fields start.
$fieldStartIndices = [regex]::Matches($headerLine, '\b\S').Index
# Calculate the field lengths.
$fieldLengths = foreach ($i in 1..($fieldStartIndices.Count-1)) {
$fieldStartIndices[$i] - $fieldStartIndices[$i - 1] - 1
}
# Get the column names
$colNames = foreach ($i in 0..($fieldStartIndices.Count-1)) {
if ($i -eq $fieldStartIndices.Count-1) {
$headerLine.Substring($fieldStartIndices[$i]).Trim()
} else {
$headerLine.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()
}
}
}
else {
# data line
$oht = [ordered] #{} # ordered helper hashtable for object constructions.
$i = 0
foreach ($colName in $colNames) {
$oht[$colName] =
if ($fieldStartIndices[$i] -lt $line.Length) {
if ($fieldLengths[$i] -and $fieldStartIndices[$i] + $fieldLengths[$i] -le $line.Length) {
$line.Substring($fieldStartIndices[$i], $fieldLengths[$i]).Trim()
}
else {
$line.Substring($fieldStartIndices[$i]).Trim()
}
}
++$i
}
# Convert the helper hashable to an object and output it.
[pscustomobject] $oht
}
}
}
}
Optional reading: potential winget.exe improvements:
The fact that winget.exe doesn't honor the console code page (as reported by chcp / [Console]::OutputEncoding) and instead invariably outputs UTF-8 is problematic, but somewhat justifiable nowadays, given that UTF-8 has become the most widely used character encoding, across all platforms, and is capable of encoding all Unicode characters, whereas the legacy Windows code pages are limited to 256 characters. Other utilities have made a similar decision, notably node.exe, the NodeJS CLI (Python is non-standard too, but has chosen the legacy ANSI code page as its default, though can be configured to use UTF-8).
In fact, it is the use of UTF-8 that enables use of … (the horizontal ellipsis character U+2026) in the output, which is a space-efficient way to indicate omission of data (the ASCII alternative would be to use ..., i.e. three (.) characters.
winget.exe's encoding behavior isn't a problem if you've configured your (Windows 10 and above) system to use UTF-8 system-wide, which, however, has far-reaching consequences - see this answer.
Now that PowerShell (Core) itself consistently defaults to UTF-8, you could argue that even if the system as a whole doesn't use UTF-8 PowerShell console windows should - see GitHub issue #7233.
winget.exe should test whether its stdout stream is connected to a console (terminal) and only then output progress information, so as to avoid polluting its stdout data output.
The currently unavoidable truncation of column values that exceed the fixed column width could be avoided with an opt-in mechanism to provide output in a structured text format that is suitable for programmatic processing, such as CSV, similar to what the (now deprecated) wmic.exe utility has always offered with its /format option.
As noted, if in the future PowerShell cmdlets that provide the same functionality as winget.exe are made available, the problem wouldn't even arise there, given PowerShell's fundamental separation between (strongly typed) data and its - selectable - for-display representation.
[1] WinGet for PackageManagement is an example of a third-party module aimed at that.
Here is my take on the problem to avoid hardcoded position of the ID column. At least on my german-localized system, the column is one char off to the left. Search for the ID word in the header row to determine how much to chop off for sorting.
# Strip two blank lines and split into header string and items array
$wgHdr, $null, $wgItems = winget list | Select-Object -skip 2
# Get the position of the 'ID' column.
$idPos = [regex]::Match( $wgHdr,'\bID\b' ).Index
# Sort beginning at the position of the 'ID' column
$wgItems | Sort-Object { $_.Substring( $idPos ) }
There's a request in the offical repo for a proper PS module, which has partially been completed, except it has to be built from source:
https://github.com/microsoft/winget-cli/tree/master/src/PowerShell/Microsoft.WinGet.Client
Although someone has packaged it using Scoop (another package manager).
https://github.com/microsoft/winget-cli/issues/221#issuecomment-1403206756
For now, it can be installed using the winget-ps Scoop package:
Install Scoop: irm get.scoop.sh | iex
Install winget-ps: scoop install winget-ps
Reload PS or import module: Import-Module Microsoft.WinGet.Client
However, there appears to be a bug in version 1.4.10173 of winget-ps, although I wouldn't rule out another issue with my troubled Windows 11 (22H2) environment. It was me.
In case that my edit on post of mklement0 may be removed; I am adding my fix (at $colNames creation) on ConvertFrom-FixedColumnTable source code at here:
Update: I removed try-catch and added a if-else check for that.
Update: The source blog is fixed at last. Tou can get code for ConvertFrom-FixedColumnTable at here. You may also take a look to my winget_list_OrderBy function at here (which uses ConvertFrom-FixedColumnTable in it) to take it a step forward.

PowerShell unable to use two -Like comparison operators together

I m learning PowerShell and one of the task I did is to filter a Csv file records.
Based on this link: https://4sysops.com/archives/create-sort-and-filter-csv-files-in-powershell/ I tried something similar to:
Import-Csv -Path '.\sample.csv' | Select-Object EmailAddress,UniqueName,LastLoginDate | ? EmailAddress -like *gmail.com -Or ? EmailAddress -like *outlook.com | Export-Csv -Path $fileOut -NoTypeInformation
But the above gives me the error mentioned in the title.
Based on this link: https://www.computerperformance.co.uk/powershell/match/ I addressed the error by using Where-Object instead after the Select-Object line as follows:
Where-Object {$_.EmailAddress -Like "*gmail.com" -Or $_.EmailAddress -Like "*outlook.com"}
Why does the first example give me error but not the second example?
tl;dr
Both your commands use the Where-Object cmdlet; ? is simply a built-in alias for it.
However, your commands use different syntax forms: your first command uses the simpler and more concise, but feature-limited individual argument-based simplified syntax, whereas your second one uses the verbose, but fully featured script-block syntax - see next section.
Because you need to combine multiple -like operations, you must use script-block syntax - simplified syntax limits you to a single operation.
Regular, script block-based syntax:
Example:
# You're free to add additional expressions inside { ... }
Where-Object { $_.EmailAddress -like '*gmail.com' }
uses a single argument that is a script block ({ ... }), inside of which the condition to test is formulated based on the automatic $_ variable that represents the input object at hand.
This syntax:
Places no constraints on the complexity of the expression - the whole PowerShell language is at your disposal inside a script block.
However, it is somewhat verbose.
Simplified, multi-argument syntax:
Example:
# Equivalent of the above.
# Note the absence of { ... }, $_, and "..."
Where-Object EmailAddress -like *gmail.com
Simplified syntax is an alternative syntax that may be used with Where-Object as well as ForEach-Object, which:
as the name implies, is simpler and less verbose.
but is limited to a single conditional / operation based on a single property, or, in the case of method calls with ForEach-Object, the input object itself.
With simplified syntax the parts that make up a conditional / method call are passed as separate arguments, which therefore bind to distinct parameters that are specifically designed to work with this syntax:
Because separate arguments are used, there is no { ... } enclosure (no script block is used).
$_ need not be referenced, because its use is implied; e.g. EmailAddress is the equivalent of $_.EmailAddress in the script block-syntax.
A notable limitation as of PowerShell 7.2.x is that with Where-Object you cannot operate on the input object itself - you must specify a property. GitHub issue #8357 discusses overcoming this limitation in the future, but there hasn't been any activity in a long time.
As usual in argument-mode parsing, quoting around string values is optional, assuming they don't contain metacharacters such as spaces; e.g., *.gmail.com - without "..." or '...' - works with simplified syntax, whereas the expression-mode parsing inside the equivalent script block requires quoting, e.g. '*gmail.com'

How to call "Invoke-MgSubscribeGroup"?

There is a PowerShell cmdlet Invoke-MgSubscribeGroup that I want to call this way:
Invoke-MgSubscribeGroup -GroupId da2d17a7-64a5-43e5-9d95-7b70333dd78c
#{ UserId = "ed3d927d-7999-459f-955d-2afc272bd4d4" }
(Split into multiple lines for better readability)
When calling it, I get en error message:
A positional parameter cannot be found that accepts argument "System.Collections.Hashtable".
Since I'm not that deep into PowerShell I must have some false understanding of how to pass a hashtable, or I have misunderstood the documentation that says:
...To create the parameters described below, construct a hash table containing the appropriate properties...
My question
What is the correct syntax to call the Invoke-MgSubscribeGroup cmdlet and pass the user ID?
To elaborate on your own answer:
PowerShell's syntax diagrams - available locally via Invoke-MgSubscribeGroup -? or Get-Command -Syntax Invoke-MgSubscribeGroup - contain all the relevant information.
That said, they're not easy to read, especially locally, and there's room for future improvement.
Quoting from Invoke-MgSubscribeGroup's documentation:
Invoke-MgSubscribeGroup
-GroupId <String>
# ...
Invoke-MgSubscribeGroup
-InputObject <IGroupsIdentity>
# ...
Each (partial) quote represents a distinct parameter set, i.e. a unique combination of parameters representing a distinct feature group.
That -GroupId and -InputObject are in different parameter sets and are exclusive to each, tells you that you cannot use them both in a given invocation (they way you mistakenly tried), i.e., that they are mutually exclusive.
Additionally, given that the parameter names -GroupId and -InputObject are not enclosed in [...] means that you can only pass named, not positional arguments to them - that is, you must precede an argument to bind to these parameters with the parameter name; e.g, -GroupId foo rather than just foo.
By convention, a parameter named -InputObject is typically used to represent values that can be supplied via the pipeline, as evidenced by the parameter's description stating Accept pipeline input: True - locally, you can see this with either Get-Help -Full Invoke-MgSubscribeGroup or - parameter-specifically - with Get-Help Invoke-MgSubscribeGroup -Parameter InputObject
Often, multiple input objects can only (meaningfully) be supplied via the pipeline; that is, -InputObject is often a mere implementation detail whose purpose is to facilitate pipeline input - see GitHub issue #4242.
GitHub issue #4135 proposes making syntax diagrams directly reflect which parameters accept pipeline input.
What complicates matters with respect to Invoke-MgSubscribeGroup's documentation, specifically (which seems to be very sparse in general):
The help topic contains no examples (which you could normally request locally with Get-Help -Examples Invoke-MgSubscribeGroup)
The data type of the -InputObject parameter, <IGroupsIdentity> (Microsoft.Graph.PowerShell.Models.IGroupsIdentity) doesn't seem to have its own documentation; it is only described in the "Notes" section of the help topic, as accepting a hashtable (#{ ... }), along with a list of the supported entries (keys and value types).
All that said: you could have passed your hashtable via the pipeline, as follows:
#{
UserId = 'ed3d927d-7999-459f-955d-2afc272bd4d4'
GroupId = 'da2d17a7-64a5-43e5-9d95-7b70333dd78c'
} | Invoke-MgSubscribeGroup
The advantage of this approach over passing an argument to -InputObject is that it would allow you to pass multiple hashtables to act on.
After further investigating and reading the documentation again and again, I've found the correct syntax:
Invoke-MgSubscribeGroup -InputObject #{
UserId = "ed3d927d-7999-459f-955d-2afc272bd4d4";
GroupId = "da2d17a7-64a5-43e5-9d95-7b70333dd78c" }
(Split into multiple lines for better readability)
I now face permission issues, but this is out-of-scope for this question, since I've asked for the correct syntax only.

Why is PS Get-ChildItem so difficult

I did a ton of reading and searching about a way to have Get-ChildItem return a dir listing in wide format, in alphabetical order, with the number of files and directories in the current directory. Here is a image of what I ended up with, but not using GCI.
I ended up writing a small PS file.
$bArgs = "--%/c"
$cArgs = "Dir /n/w"
& cmd.exe -ArgumentList $bArgs $cArgs
As you can see I ended up using the old cmd.exe and passing the variables I wanted. I made an alias in my PS $Profile to call this script.
Can this not be accomplished in PS v5.1? Thanks for any help or advice for an old noob.
PowerShell's for-display formatting differs from cmd.exe's, so if you want the formatting of the latter's internal dir command, you'll indeed have to call it via cmd /c, via a function you can place in your $PROFILE file (note that aliases in PowerShell are merely alternative names and can therefore not include baked-in arguments):
function lss { cmd /c dir /n /w /c $args }
Note that you lose a key benefit of PowerShell: the ability to process rich objects:
PowerShell-native commands output rich objects that enable robust programmatic processing; e.g., Get-ChildItem outputs System.IO.FileInfo and System.IO.DirectoryInfo instances; the aspect of for-display formatting is decoupled from the data output, and for-display formatting only kicks in when printing to the display (host), or when explicitly requested.
For instance, (Get-ChildItem -File).Name returns an array of all file names in the current directory.
By contrast, PowerShell can only use text to communicate with external programs, which makes processing cumbersome and brittle, if information must be extracted via text parsing.
As Pierre-Alain Vigeant notes, the following PowerShell command gives you at least similar output formatting as your dir command, though it lacks the combined-size and bytes-free summary at the bottom:
Get-ChildItem | Format-Wide -AutoSize
To wrap that up in a function, use:
function lss { Get-ChildItem #args | Format-Wide -Autosize }
Note, however, that - due to use of a Format-* cmdlet, all of which output objects that are formatting instructions rather than data - this function's output is also not suited to further programmatic processing.
A proper solution would require you to author custom formatting data and associate them with the System.IO.FileInfo and System.IO.DirectoryInfo types, which is nontrivial however.
See the conceptual about_Format.ps1xml help topic, Export-FormatData, Update-FormatData, and this answer for a simple example.

Running help <command> and piping output to Where-Object or Select-Object returns empty rows

Running the command help firewall | Select-Object Category. The result is one column blank Category.
The strange thing is that the empty rows number represent the amount of rows that help firewall would result without calling piping it to Select-Object
Or I'm trying to filter the output of help firewall to return only rows with Name that starts with "Get". Running help firewall | Where-Object Name -like "Get" just returns nothing.
Why aren't these pipes on help working? They are working perfectly on other commands.
Powershell Version 5.1 and using default windows console.
To complement Zilog80's helpful answer with background information:
Get-Command help reveals that help is not a mere alias of the Get-Help cmdlet, but a (built-in) function (submit $function:help to see its definition).
As you've noticed yourself:
while Get-Help outputs an object ([pscsustomobject]) with properties that reflect help-topic metadata such as Category, which is then rendered as display text by PowerShell's output-formatting system,
the help function returns strings - a stream of text lines representing the rendered help topic - of necessity.
You can observe the difference in output type by piping to the Get-Member cmdlet (help firewall | Get-Member vs. Get-Help firewall | Get-Member)
The purpose of the help function is to wrap Get-Help with interactive paging, to allow convenient navigation through lengthy help topics that don't fit onto a single console (terminal) screen.
This paging is provided via an external program (by default, more.com on Windows, and less on Unix-like platforms, configurable via $env:PAGER, but only in PowerShell (Core) 7+), and since PowerShell only "speaks text" when communicating with external programs, help must send a stream of strings (lines for display) to them, which it does via
Out-String -Stream.
Note:
When the external paging programs find that their stdout stream isn't connected to a console (terminal), they take no action other than simply passing the input through (in Unix terms, they then behave like cat).
Hypothetically, the help function itself could determine this condition and then (a) not pipe to the paging program and (b) relay the object output by Get-Help as-is.[1] However, determining a command's output target from inside that command, using PowerShell code, may not even be possible.
[1] The function actually already takes this action when a custom pager defined via $env:PAGER is found to be a PowerShell command rather than an external program.
Check the feedback from help help in PowerShell :
You can also type `help` or `man`, which displays one screen of text at a
time. Or, ` -?`, that is identical to `Get-Help`, but only
works for cmdlets.
The helpcommand display "screen of text" which means it is outputting [System.String] objects, not [PSCustomObject] objects.
Only -? will behave identically to Get-help and will provide [PSCustomObject] objects.
To see what's going on, check the different output from :
help firewall | %{ $_.GetType() }
And
Get-help firewall | %{ $_.GetType() }
And, for cmdlet,
Select-Object -? | %{ $_.gettype() }