Is it possible to use a cmdlet as an input parameter for a dynamic PowerShell script? - powershell

Here's a test script I'm trying to use, and I'm calling it from a separate process and attempting to pass parameters to it. The idea is that I have a user interface that allows a user to select a CmdLet and then populate another dropdown with the properties/methods of that CmdLet.
My problem seems to be that the script is rendering the input parameter as a string, and is thusly creating a text file with the methods and properties of any arbitrary string to which you've applied a "Get-Member" to, such as "Clone", or "CompareTo". The only property as such is "Length".
Is there any way to have that input parameter be brought over as a usable CmdLet instead of a string? Perhaps I'm missing something, or perhaps what I'm attempting to do isn't possible.
param([string]$inputCmdLet = "Get-NetAdapter");
$wrkgDir = "D:\Distribution\Operational";
# Get Properties and Methods for CmdLet Input Parameter
$propertyNames = $inputCmdLet | Get-Member -MemberType Property;
$methodNames = $inputCmdLet | Get-Member -MemberType Method;
# Sort Arrays
$propertyNames = $propertyNames | Sort-Object Name;
$methodNames = $methodNames | Sort-Object Name;
# Output Results to Text Files
$propertyNames.Name | Out-File $wrkgDir\$inputCmdLet.Properties.txt;
$methodNames.Name | Out-File $wrkgDir\$inputCmdLet.Methods.txt;
EDIT FOR MORE INFO:
The output I'm hoping for, in the example of Get-NetAdapter, is the list of properties in one output file and methods in the other. What I'm getting now is this:
Left list is expected (partial) result, right list is actual result.
I'm uncertain how to achieve the result list on the left (in the image) programmatically. I'm able to get the proper output by typing it out statically:
$mbrNameStatic = Get-NetAdapter | Get-Member;
$mbrNameStatic.Name | Out-File $wrkgDir\$inputCmdLet.Strings.txt;
But when i use the input parameter, it merges the value in as a string, so it seems the actual runtime code looks more like this:
$propertyNames = "Get-NetAdapter" | Get-Member -MemberType Property;
So the addition of the quotes renders the cmdlet as a string (makes sense i suppose, since my input parameter is a string), which returns the properties and methods of a string instead of the cmdlet. Is there any way to have the cmdlet render out without the quotes?
Please do let me know if I'm not making sense with this, either with my description, or with the idea altogether.
Thanks!

In order to execute a command whose name (only) is stored in a variable or whose name is specified in single or double quotes, you must use &, the call operator.
# WRONG: The token is interpreted as an *expression* that outputs a *string*
"Get-NetAdapter" # outputs the [string] literal
# WRONG: ditto, via a variable
$name = "Get-NetAdapter"
$name # outputs the contents of the [string] variable
# OK: Use of & tells Powershell to interpret the next token as a *command* to *invoke*.
& "Get-NetAdapter"
& $name
As for your general approach:
Note that not all cmdlets produce output when invoked without parameters, so your current code (even with &) won't work with all cmdlets.
Conversely, those cmdlets that do produce output when given no parameters may produce a lot of them, which is unnecessary, so consider something like & $inputCmdlet | Select-Object -First 1.
Generically, you can use something like (Get-Command Get-NetAdapter).OutputType to obtain a cmdlet's output type(s), but note that:
Declaring output types is optional, so not all cmdlets may return a value.
If you start with a type rater than an instance of that type, you cannot use Get-Member to discover the instance members (you can only obtain the static members via -Static).

Related

Powershell - Get object properties displayed in console

The point is to populate a variable with a Powershell object's property name displayed in a console.
Meaning, if I run Get-Process, I only want the eight object's properties returned in the console which are 'Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName'.
Get-Member command is not helpful here.
Can anyone help me with that?
Thank you all!
To get the column names - which may or may not be property names - of the table view that is presented for a given .NET type if it has predefined formatting data (that includes a table view) associated with it:
Note:
The following is a proper, but nontrivial and limited solution that derives the column names from the formatting data, using the first table-view definition found. It also has conceptual background information.
See the bottom section for a quick-and-dirty solution for getting the column names only, which uses text parsing to extract the column names directly from a given command's formatted output.
The middle section builds on this first section and extracts a list of property names and calculated properties mirroring the column definitions, which can be used with Select-Object, in order to create custom objects that have properties with the same values that the formatting data produces.
# Determine the .NET type of interest.
$type = (Get-Process)[0].GetType()
# Extract the names of the column headers from the *first* table-view definition.
Get-FormatData $type -PowerShellVersion $PSVersionTable.PSVersion |
ForEach-Object FormatViewDefinition |
Where-Object Control -is [System.Management.Automation.TableControl] |
Select-Object -First 1 |
ForEach-Object {
$i = 0
$rows = $_.Control.Rows
foreach ($colLabel in $_.Control.Headers.Label) {
if ($colLabel) { $colLabel } # Explicit label, with a calculated column value or renamed property
else { $rows.Columns[$i].DisplayEntry.Value } # Property name, with its value as the column value.
++$i
}
}
Caveat: The above limits output to the first table-view definition found - which may or may not apply to a given command. Which definition is chosen by default is potentially governed by criteria associated with the definitions that select based on runtime conditions, including selecting by specific input type, given that a single instance of formatting data can cover multiple types.
Also note that views may involve grouping (as you see in Get-ChildItem's formatted output, for instance), and the grouping criterion isn't covered by the command above.
Note that even for a single type multiple views may be defined, and in order to use a non-default one you must request it explicitly, via Format-Table's -View parameter, assuming you know the name,[1] e.g. Get-Process | Format-Table -View StartTime).
See also:
This answer for how to inspect formatting data in full.
You can alternatively pipe Get-FormatData output to Export-FormatData in order to export formatting data to an XML file, which has the disadvantage of being hard to read, but has the advantage of matching the XML schema used for authoring formatting data - see next point - whereas the in-memory types used to represent formatting data partially use property names that don't match the underlying XML elements.
As for authoring formatting data, which as of PowerShell 7.2.2 requires XML files (*.Format.ps1xml):
See this answer for an example of how to define your own table view.
Formatting File Overview and the Format Schema XML Reference
Note:
Using -PowerShellVersion $PSVersionTable.PSVersion with Get-FormatData is only needed in Windows PowerShell, for certain types, to work around a bug that is no longer present in PowerShell (Core) 7.1+
While column names typically correspond to the property names of the type instances being formatted, that isn't always the case, such as with the [System.Diagnostics.Process] instances output by Get-Process.
A general caveat, as zett42 notes, is that display formatting of types isn't part of the public contract regarding breaking changes, so formatting definitions are allowed to change over time.
If a given type has no predefined formatting data associated with it (in which case Get-FormatData is a quiet no-op):
The names of its (public) instance properties are used as column
names.
You only get a table view by default if there are 4 or fewer properties but you can request it explicitly with Format-Table (With 5 or more properties, Format-List is applied by default).
To get the names of all (public) instance properties of a given object, use the intrinsic .psobject property, which is a rich source of reflection; e.g.:
(Get-Process | Select-Object -First 1).psobject.Properties.Name
To create a list of property names and calculated properties usable with Select-Object that mirror the formatting-data's column definition:
# Determine the .NET type of interest.
$type = (Get-Process)[0].GetType()
# Get an array of property names / calculated properties from the
# formatting data, for later use with Select-Object
$props =
Get-FormatData $type -PowerShellVersion $PSVersionTable.PSVersion |
ForEach-Object FormatViewDefinition |
Where-Object Control -Is [System.Management.Automation.TableControl] |
Select-Object -First 1 |
ForEach-Object {
$i = 0
$rows = $_.Control.Rows
foreach ($colLabel in $_.Control.Headers.Label) {
if ($colLabel) { # Explicit label, with a calculated column value or renamed property
#{
Name = $colLabel
Expression = if ('ScriptBlock' -eq $rows.Columns[$i].DisplayEntry.ValueType) {
[scriptblock]::Create($rows.Columns[$i].DisplayEntry.Value)
} else {
$rows.Columns[$i].DisplayEntry.Value
}
}
}
else { # Property name, with its value as the column value.
$rows.Columns[$i].DisplayEntry.Value
}
++$i
}
}
# Sample call
Get-Process | Select-Object -Property $props | Format-Table | more
The sample call produces similar output to just Get-Process alone, as it uses the column definitions as (calculated) properties - albeit with default values for formatting attributes such as column width and alignment.
Note the explicit use of Format-Table to ensure tabular output; without it - given that the [pscustomobject] instances created by Select-Object have no formatting data associated with them - list formatting (implied Format-List) would result.
As Mathias points out, the calculated properties will be string-typed even for columns based on numeric properties, because their purpose in the formatting data is to created formatted string representations.
Quick-and-dirty solution for getting the column names only:
The following uses Out-String -Stream in conjunction with Select-String to extract the column names from a given command's formatted output, which relies on two assumptions:
The column names have no embedded spaces
The command actually produces table-formatted output by default; however, you can insert a Format-Table call before Out-String, if desired.
Get-Process | Out-String -Stream | Select-String -List '^\s*--+' -Context 1, 0 |
ForEach-Object { -split $_.Context.PreContext[0] }
Output:
NPM(K)
PM(M)
WS(M)
CPU(s)
Id
SI
ProcessName
Note: In Windows PowerShell an additional property shows, as the first one: Handles.
[1] While tab-completion does offer view names, they appear to be out of sync with the actually available ones, as of PowerShell 7.2.2. To see the latter, provoke an error with a dummy name, and the error message will list the available ones; e.g. Get-Process | Format-Table -View NoSuch lists the following available views in the resulting error message: process, Priority, StartTime

PowerShell custom output #{n=;e=}

I'm not very clear on how the #{n=;e=} construct works in PowerShell.
Does this type of thing have a name that I can find examples from?
For example, I find examples like this that works great:
gwmi win32_logicaldisk | Format-Table DeviceId, VolumeName, #{n="Size(GB)";e={[math]::Round($_.Size/1GB,2)}},#{n="Free(GB)";e={[math]::Round($_.FreeSpace/1GB,2)}}
When I try to do something like that, I can never get it to work. This works fine:
Get-Command -Module Microsoft.Powershell.Utility | Where CommandType -eq Function | Select Name,Version,CommandType
So I thought I would try and add the definition of that function to a new column using cat function:\$_.Name
Get-Command -Module Microsoft.Powershell.Utility | Where CommandType -eq Function | Select Name,Version,CommandType,#{n="Contents"; e={cat function:\$_.Name}}
But I just get an empty Contents column :(
Can someone give me some pointers on how the #{n=;e=} construct works?
Also, what do the n and e stand for?
#{n='';e={}} syntax is called a calculated property. n stands for Name and e stands for expression. You can even specify Name instead of n and Expression instead of e.
Calculated properties allow you to create new properties or change existing ones. This is done by passing a special hashtable to the Property parameter rather than a static property name. This is a useful feature where you create new properties using custom expression in a script block and use existing properties.
Not for only Select-Object this work for also Format-Table, Format-List cmdlets. These don't work outside this cmdlets.
Calculated properties are a quick way to manipulate command output to return just about anything you like. These save your time and reduce code length.
Sidenote: The last code in your question dosent work because you need to join two path using Join-Path. Calculated properties are innocent here. Even you can join path like this: cat "Function:\$($_.Name)" as #MathiasR.Jessen pointed.
#{} is a hashtable. {} is a scriptblock inside the hashtable set equal to e. The hashtable is used in a custom way for select-object.
$scriptblock = { $_ }
$hashtable = #{ name = 'number'
expression = $scriptblock
}
1..3 | select-object -property $hashtable
number
------
1
2
3

Remove the at symbol ( # ) and curly bracket ( { ) from Select-Sring output in Powershell

I'm parsing filenames in Powershell, and when I use Get-ChildItem | select name, I get a clean output of the files:
file1.txt
file2.txt
file3.txt
But when I try to narrow down those files with Select-String, I'm getting a weird # and { in front of my output:
Get-ChildItem | select name | Select-String -Pattern "1"
#{file1.txt}
Is there a parameter I'm missing? If I pipe with findstr rather than Select-String it works like a charm:
Get-ChildItem | select name | Findstr "1"
file1.txt
You can simplify and speed up your command as follows:
#((Get-ChildItem).Name) -match '1'
Note: #(), the array-subexpression operator, is needed to ensure that -match operates on an array, even if only one file happens to exist in the current dir.
(...).Name uses member-access enumeration to extract all Name property values from the file-info objects returned by Get-ChildItem.
-match, the regular-expression matching operator, due to operating on an array of values, returns the sub-array of matching values.
To make your original command work:
Get-ChildItem | select -ExpandProperty Name |
Select-String -Pattern "1" | select -ExpandProperty Line
select -ExpandProperty Name makes select (Select-Object) return only the Name property values; by default (implied -Property parameter), a custom object that has a Name property is returned.
select -ExpandProperty line similarly extracts the Line property value from the Microsoft.PowerShell.Commands.MatchInfo instances that Select-String outputs.
Note that in PowerShell [Core] v7+ you could omit this step by instead using Select-String's (new) -Raw switch to request string-only output.
As for what you tried:
As stated, by not using -ExpandProperty, select name (implied -Property parameter) created a custom object ([pscustomobject] instance) with a Name property.
Select-String stringifies its input objects, if necessary, so it can perform a string search on them, which results in the representation you saw; here's a simulation:
# Stringify a custom object via an expandable string ("...")
PS> "$([pscustomobject] #{ Name = 'file1.txt' })"
#{Name=file1.txt}
As an aside:
The above stringification method is essentially like calling .ToString() on the input objects[1], which often results in useless string representations (by default, just the type name); a more useful and intuitive stringification would be to use PowerShell's rich output-formatting system, i.e. to use the string representation you would see in the console; changing Select-String's behavior to do that is the subject of this feature request on GitHub.
[1] Calling .ToString() directly on a [pscustomobject] instance is actually still broken as of PowerShell Core 7.0.0-rc.2, due to this bug; the workaround is to call .psobject.ToString() or to use an expandable string, as shown above.

using powershell and pipeing output od Select-Object to access selected columns

I have the power shell below that selectes certain fields
dir -Path E:\scripts\br\test | Get-FileMetaData | Select-Object name, Comments, Path, Rating
what i want to do is utilize Name,Comments,Path,Rating in further Pipes $_.name etc dosnt work
If I understand your question correctly, you want to do something with the output of Select-Object, but you want to do it in a pipeline.
To do this, you need to pass the output down the pipeline into a Cmdlet that accepts pipeline input (such as ForEach-Object). If the next operation in the pipeline does not accept pipeline input, you will have to set the output to a variable and access the information through the variable,
Using ForEach-Object
In this method, you will be processing each object individually. This will be similar to the first option in Method 1 (that is, dealing with individual items in the collection of items returned by Select-Object).
dir | Get-FileMetaData | Select-Object Name,Comments,Path,Rating | ForEach-Object {
# Do stuff with $_
# Note that $_ is a single item in the collection returned by Select-Object
}
The variable method is included in case your next Cmdlet does not accept pipeline input.
Using Variable
In this method, you will treat $tempVariable as an array and you can operate on each item. If need be, you can actually access each column individually, getting everything at once.
$tempVariable = dir | Get-FileMetaData | Select-Object Name,Comments,Path,Rating
# Do stuff with each Name by using $tempVariable[i].Name, etc.
# Or do stuff with all Names by using $tempVariable.Name, etc.

Is there an equivalent of "this" in powershell?

Basically I have this code:
$file = $web.GetFile("Pages/default.aspx")
$file.CheckOut()
and I was wondering if there is anyway to use a pipe and the powershell equivalent of this to rewrite it as:
$web.GetFile("Pages/default.aspx") | $this.CheckOut()
When I try this I get the error:
Expressions are only allowed as the first element of a pipeline.
I also tried using $_ instead of $this but got the same error.
Actually there is a $this in a few cases. You can create a ScriptProperty or ScriptMethod and attach it to an object, and $this will be the original object. You can then define these in types files (I'd recommend using the module EZOut, it makes life much easier) so that any time you see that type, you get that method.
For example:
$Web | Add-Member ScriptMethod EditFile { $this.Checkout() }
Hope this helps
What you're looking for is $_ and it represents the current object in the pipeline. However you can only access $_ in a scriptblock of a command that takes pipeline input e.g.:
$web.GetFile("Pages/default.aspx") | Foreach-Object -Process {$_.Checkout()}
However there are aliases for the Foreach-Object cmdlet {Foreach and %} and -Process is the default parameter so this can be simplified to:
$web.GetFile("Pages/default.aspx") | Foreach {$_.Checkout()}
One other point, the GetFile call appears to return a single file so in this case, the following would be the easiest way to go:
$web.GetFile("Pages/default.aspx").Checkout()
Of course, at this point you no longer have a variable containing the file object.
$_ is the variable for "current object" in powershell.
However, you aren't passing any data, this is just variable assignment. You can only use the pipeline if you manipulate the actual output of a command and use it as input down the pipeline.
I think what you want can be accomplish with nested parentheses:
($web.GetFile("Pages/default.aspx")).CheckOut()
In PS, anything you put inside parentheses gets treated as its own object, and you can apply methods to that inline without variable reassignment.
Assignment does silence the default output, but it does not prevent an object from being further referenced.
($file = $web.GetFile("Pages/default.aspx")).CheckOut()
Of course, it's much more common to either store the return value in a variable and do stuff with it or chain methods/properties/pipes.