How select-object cmdlet accepts input by pipeline? - windows

Sorry for asking a novice question, as I am learning Powershell and I am confused how select-object -property parameter works with pipeline. As mentioned in help it doesnot accept the value through pipeline.
For e.g: the below code should have given an error:
get-process|select-object -property name,vm,pm
Can someone explain or guide me, thanks in advance.

To better understand how Select-Object works, here is a very simplified demo function that works similar to Select-Object:
function Select-ObjectSimplified {
[CmdletBinding()]
param (
# "ValueFromPipeline" means this parameter accepts pipeline input
[Parameter(Mandatory, ValueFromPipeline)] [PSObject] $InputObject,
# This parameter does NOT accept pipeline input
[Parameter(Mandatory)] [Object[]] $Property
)
# The process section runs for each object passed through the pipeline.
process {
# Create an ordered hashtable that will store the names and values
# of the selected properties.
$OutputObject = [ordered] #{}
# Loop over each property of $InputObject
foreach( $InputObjectProperty in $InputObject.PSObject.Properties ) {
# Check if the current property is listed in -Property argument.
if( $Property -contains $InputObjectProperty.Name ) {
# Add the current property to the output object.
$OutputObject[ $InputObjectProperty.Name ] = $InputObjectProperty.Value
}
}
# Convert the hashtable to a PSCustomObject and (implicitly) output it.
[PSCustomObject] $OutputObject
}
}
Demo:
# Create an array of two objects.
$example = [PSCustomObject]#{ Foo = 4; Bar = 8 },
[PSCustomObject]#{ Foo = 15; Bar = 16 }
# Pass the array as input to the pipeline.
$example | Select-ObjectSimplified -Property Foo | Format-List
Output:
Foo : 4
Foo : 15
Although the parameter -Property doesn't accept pipeline input, we can still use it when we process the pipeline input that binds to parameter -InputObject. There is no need for -Property to accept pipeline input, because it stays constant during the whole run of the pipeline.
The demo is executed by PowerShell like this:
$example | Select-ObjectSimplified -Property Foo | Format-List
The argument "Foo" gets bound to parameter -Property. The parameter -InputObject is not bound yet, because we didn't explicitly pass an argument to it.
The first element of the array $example is passed through the pipeline. The argument [PSCustomObject]#{ Foo = 4; Bar = 8 } gets bound to parameter $InputObject.
The process{} section runs. There we can get the current pipeline object from $InputObject and get the argument of parameter -Property from $Property. So $InputObject will be different for each run of process{}, but $Property does not change, it is constant.
The second element of the array $example is passed through the pipeline. The argument [PSCustomObject]#{ Foo = 15; Bar = 16 } gets bound to parameter $InputObject.
Like 3), but with different value for $InputObject.
Hope that shed some light on the topic. To get an even better understanding I suggest to read About Pipelines and then follow a tutorial to write your own pipeline function. The concept only did really click for me, once I successfully wrote my first real pipeline functions.

Related

How to Stop powershell form Converting the variable assigment of a boolean to Uppercase letter

I am trying to format a json and do dynamic variable assignment but once the boolean value is assigned powershell is changing the casetype for the first letter to uppercase.
I still want to maintain the case type for my input values both lowercase and uppercase as it is in my json file.
Any help?
{
"input": true,
"variable": "Relative path",
}
$path= "L:\test\parameter.json"
$json = Get-Content $path | ConvertFrom-Json
foreach ($data in $json.PSObject.Properties) { New-Variable -name $($data.name) -value $($data.value) -Force}
echo $input
True ->>> I want it to be "true" and the value of variable to still be "Relative Path"
Generally, you mustn't use $input as a custom variable, because it is an automatic variable managed by PowerShell.
Leaving that aside, ConvertFrom-Json converts a true JSON value - a Boolean - into the equivalent .NET Boolean (System.Boolean, represented as [bool] in PowerShell). The representation of this value in PowerShell is $true.
Printing this value to the console (host) effectively calls its .ToString() method in order to obtain a string representation, and that string representation happens to start with an uppercase letter:
PS> $true
True
If you need an all-lowercase representation, call .ToString().ToLower(), or, for brevity, use an expandable string and call .ToLower() on it:
PS> "$true".ToLower() # In this case, the same as $true.ToString().ToLower()
true
If you want to apply the all-lowercase representation automatically to all Booleans, you have two options:
Modify the data, by replacing the Boolean values with their desired string representations:
This answer shows how to walk a [pscustomobject] object graph returned by ConvertFrom-Json and update its (leaf) properties.
Preferably, only modify the display formatting of [bool] values, without needing to modify the data, as zett42 suggests.
See below.
(Temporarily) overriding the .ToString() method of type [bool]:
Update-TypeData can be used to override the members of arbitrary .NET types, but there is a limitation due to a bug - reported in GitHub issue #14561 - present up to at least PowerShell 7.2.2:
A .ToString() override is not honored when you cast an instance to [string] (e.g., [string] $true) or when you use it in an expandable string (e.g, "$true")
However, with implicit stringification of Booleans, as happens during for-display formatting, it does work:
# Override the .ToString() method of [bool] (System.Boolean) instances:
# Save preexisting type data, if any.
$prevTypeData = Get-TypeData -TypeName System.Boolean
# Add a ScriptMethod member named 'ToString' that outputs an
# all-lowercase representation of the instance at hand. ('true' or 'false')
Update-TypeData -TypeName System.Boolean `
-MemberType ScriptMethod -MemberName ToString `
-Value { if ($this) { 'true' } else { 'false' } } `
-Force
# Output a sample custom object with two Boolean properties.
[pscustomobject] #{
TrueValue = $true
FalseValue = $false
}
# Restore the original behavior:
# Note: In production code, it's best to put this in the `finally`
# block of try / catch / finally statement.
# Remove the override again...
Remove-TypeData -TypeName System.Boolean
# ... and restore the previous data, if any.
if ($prevTypeData) { Update-TypeData -TypeData $prevTypeData }
Note: You cannot scope Update-TypeData calls, which invariably take effect session-globally, so it's best to remove the override again with Remove-TypeData and restore any preexisting type data, if any, as shown above.
zett42 has generalized the approach above to create a general-purpose Invoke-WithTemporaryTypeData function that scopes type-data modifications to a given piece of code (script block): see this Gist.
Output (note the all-lowercase property values):
TrueValue FalseValue
--------- ----------
true false

powershell pass array to another function

I have a function that gets all the processes accessing a particular folder and split it's process id. In this function, all the ids are saved in an array. I want to give this array to another function. The code i have is:
$CLRJson = Get-Content -Raw -Path "C:\Users\Lokal-keeran\Documents\Ausbildung\Aufgaben\Powershell\PI-Kill\PIKill.json" | ConvertFrom-Json
##Directory path to be scanned to see if there are processes accessing the files in the given directory.
$CLRDIRProd = $CLRJson.CLRExtproc_PARA_PROD.CLRDIR
$PIDGF = #()
#Get all Processes, accessing a folder
function Get-FileHandle ($HPath){
$handle = handle.exe $HPath
[System.Collections.ArrayList]$Process = $handle | Select-String -Pattern '.*?(?= +type:)' | Select-String -Pattern 'mstsc.exe' -NotMatch
Foreach ($pro in $Process.Matches.Value) {
$AddtoArray = $pro -split(":") | Select-Object -Last 1
$PIDGF += $AddtoArray
}
}
#Stop the Processes accessing that folder
function stop-FileHandle ([string[]]$HPathtoGFH) {
Get-FileHandle -HPath $CLRDIRProd
"The Third Value of this Array is: $PIDGF[2]"
#Stop Command......
}
stop-FileHandle -HPathtoGFH $CLRDIRProd
If i revoke stop-filehandle no output will be shown. Also the global array doesn't contain any value. But if i run the commands in the function Get-Filehandle, it shows me some process ids one below the another, just like it should be.....
Why array could not be given to another function ?
Inside function Get-FileHandle, stuff is added to a function local variable called $PIDGF.
Since you have declared your array outside the function, you should use script-scoping inside the function:
$script:PIDGF += $AddtoArray
Then in the stop-FileHandle function you need to use a subexpression ($())around the value you want to output:
"The Third Value of this Array is: $($script:PIDGF[2])"
Much better would be if the Get-FileHandle simply outputs the array so the calling function can capture that in a variable:
function Get-FileHandle ($HPath){
$handle = handle.exe $HPath
[System.Collections.ArrayList]$Process = $handle |
Select-String -Pattern '.*?(?= +type:)' |
Select-String -Pattern 'mstsc.exe' -NotMatch
foreach ($pro in $Process.Matches.Value) {
# just output the values, so the calling function receives an array
$pro -split(":") | Select-Object -Last 1
}
}
#Stop the Processes accessing that folder
function Stop-FileHandle ([string[]]$HPathtoGFH) {
$PIDGF = Get-FileHandle -HPath $CLRDIRProd
"The Third Value of this Array is: $($PIDGF[2])"
#Stop Command......
}
Stop-FileHandle -HPathtoGFH $CLRDIRProd
This way you do not use += concatenation to an existing array which is both time and memory consuming, plus you do not have to worry about scoping.

Powershell problem with service status searching

Hi guys its maybe a easy question for you but im newbie from powershell so can you pls help me?
In school I got an assignment where I needed to make a menu, a script that could search from service to status, and a script that could search from status to service, and it looks like this:
elseif ($menu -eq "2") {
$statusbank = (Get-Service).Status
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if ($statusbank.Contains([string]$sstatuss)) {
$Information = (Get-Service | Where-Object {$_status -eq $sstatuss}).Name | format-list -property Name
Write-Host $Information
}
}
i really dont understand where my problem is.
It dosn't work: It doesn't do anything and then just ends the script
If i debug, i only see it will skip this, even they are a lot of true value in $statusbank :
if ($statusbank.Contains([string]$sstatuss)) {
Try using this instead:
elseif ($menu -eq "2")
{
$statusbank = Get-Service
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if($sstatuss -match '^(Running|Stopped)$' -and $sstatuss -in $statusbank.Status)
{
$statusbank | Where-Object Status -EQ $sstatuss |
Format-Table -Property Name,Status
}
}
To complement Santiago Squarzon's helpful answer with an optimization:
# Prompt until a valid service status identifier is entered.
do {
try {
[System.ServiceProcess.ServiceControllerStatus] $sStatus =
Read-Host "Please specify the desired service status (e.g., Running or Stopped)"
break # A valid value was entered, exit the loop
} catch { }
Write-Warning "Unknown status; please specify one of: $([Enum]::GetNames([System.ServiceProcess.ServiceControllerStatus]))"
} while ($true)
# Now output the names of all services that are in the specified state, if any:
(Get-Service | Where-Object Status -eq $sStatus).Name
Casting the user input (which is always a string) to type [System.ServiceProcess.ServiceControllerStatus] (the type of the .Status property of the objects returned by Get-Service) is used to ensure that a valid service-status identifier was entered.
As for what you tried:
Leaving the inefficiency of calling Get-Service twice aside, your primary problem was the use of the .Contains() .NET array method (implemented via the IList interface):
.Contains() performs no on-demand type conversions, so looking for a string ($sstatuss) in your array of [System.ServiceProcess.ServiceControllerStatus] values ($statusbank) never succeeds.
By contrast, PowerShell's -contains operator does perform on-demand type conversions (as PowerShell generally does) and is notably also case-insensitive (as PowerShell generally is). The same applies to functionally equivalent, but operands-reversed -in operator.
To illustrate the difference:
# Sample array with [System.ServiceProcess.ServiceControllerStatus] elements.
$array = [System.ServiceProcess.ServiceControllerStatus]::Running,
[System.ServiceProcess.ServiceControllerStatus]::Stopped
# WRONG.
$array.Contains('running') # !! always $false with a [string] as input
# OK.
$array -contains 'running' # -> $true - on-demand type conversion
# from string to [System.ServiceProcess.ServiceControllerStatus]
In a nutshell: -contains is in effect using the -eq operator against each element behind the scenes, so the latter's automatic type conversions and case-insensitivity apply. See the bottom section of this answer for more information about -contains and -in.
Pitfall: Due to having the same name, there's potential for confusion with the .Contains() string method, which functions differently, however: it performs literal substring matching, and there is no direct operator equivalent in PowerShell for that - see this answer.
Also:
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer

How does one Transform a Collection of Objects into a Collection of new Objects of another Type in Powershell?

Supposing I have a method that gets a collection of enums of my enum type ProjectName and a have a collection of Server objects I want to associate with those ProjectNames in a type called Project; how would I do this in a Powershell Select-Object (or some other equivalent of LINQ's Select).
The C# equivalent of what I want to produce in Powershell is this:
var servers = new[]
{
new Server(/*someOtherProjectsCollection goes here*/),
new Server(/*someOtherProjectsCollection goes here*/),
new Server(/*someOtherProjectsCollection goes here*/)
};
var projects = GetProjectNames().Select(projectName => new Project(projectName, servers.Where(server => server.Projects.Any(serverProject => serverProject.Name == projectName))));
But what I have is this:
$servers = [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/)
$projects = (GetProjectNames()) | Select-Object {
$selectedProjectName = $_
return [Project]::new($_, ($servers | Where-Object { $_.projects.Where({ $_ -eq $selectedProjectName }).Count -gt 0 }))
}
When I try and read $projects back in Powershell LSE (whilst on a breakpoint after this last line), it just returns the code as a string and I can't even cast it to [Project[]]. I think the problem might be with the use of curly braces with Select-Object but I'm not sure how else to create a new Project object within the Select-Object.
You want ForEach-Object instead of Select-Object to return a new [Project] instance for each project name; also, your code can be streamlined:
$projects = GetProjectNames | ForEach-Object {
$projectName = $_
[Project]::new(
$projectName,
$servers.Where({ $_.projects -eq $projectName })
)
}
Select-Object is for creating new custom objects based on select properties from the input objects; by contrast, you're constructing a new, specific type instance from each input object, which must be done in a ForEach-Object call, where you explicitly control the output.
$_.projects -eq $projectName as a conditional relies on PowerShell's ability to use -eq with an array as the LHS, in which case filtering is performed, and a filtered subarray is returned; since .Where() interprets the script block's output as a Boolean, an empty subarray is interpreted as $false, whereas one with at least one element is interpreted as $true.
Also note that you don't need an explicit return, given PowerShell's implicit output behavior: since the newly constructed [Project] instance isn't assigned to a variable or sent elsewhere, it is automatically returned.

How to use default value when given an empty string in powershell function?

I would like to use the default value when given an empty string. I am hoping for something more elegant then having an if statement to check if $Var is empty, and setting it to a default. Anyone know if this can be achieved? Also need to support powershell version 2.0.
Below is a snippet of what I'm trying to accomplish. Given an empty string I would like it to print "Var: DEFAULT".
$ErrorActionPreference = "Stop"
function Test-Function(
[parameter( Mandatory = $true )][string] $Var = "DEFAULT"
) {
# If Mandatory is set to $true an error is thrown
# "Cannot bind argument to parameter 'password' because it is an empty string."
# When Mandatory is set to $false, $Var is an empty string rather than "DEFAULT"
Write-Host "Var: $Var"
}
$EmptyString = ""
Test-Function -Var $EmptyString
Var is an empty string because you are explicitly passing an empty string. Empty strings are still objects, not null. Your call to Test-Function -Var $EmptyString fails to give you the output you are looking for as you are equating an empty string and null which is false in .Net. Your statement that "When Mandatory is set to $false, $Var is an empty string rather than 'DEFAULT'" is correct as you did pass something, an empty string so the assignment of the value "DEFAULT" was never called.
You could remove the Mandatory=$true in which case your "Default" value is displayed when the parameter is not passed.
function Test-Function(
[parameter( Mandatory = $false )]
[string] $Var = "DEFAULT"
){
Write-Host "Var: $Var"
}
Test-Function
This generates Var: DEFAULT as expected.
Regardless of whether the parameter is mandatory or not, if you pass an empty string the assignment to $Var = "Default" is never reached as $Var has a value of '' which while empty is actually a string.
Test-Function ''
This generates Var: Which may look like wrong but it output the empty string you told it to.
If you want to allow a default value to be assigned when the parameter is not passed use the Mandatory=$false and assign a default value as I did above. If you want to test the value that was passed and assign a default value if it was an empty string you should do that in the begin block of your function.
function Test-Function(
[parameter( Mandatory = $false )]
[string] $Var = "DEFAULT"
){
begin{if([String]::IsNullOrWhiteSpace($Var)){$Var="DEFAULT"}}
process{Write-Host "Var: $Var"}
end{}
}
Test-Function
Test-Function ''
Test-Function 'SomeValue'
This generates the following which I believe to be what you expected:
Var: DEFAULT
Var: DEFAULT
Var: SomeValue
There is no elegant way to do that. At least there is not a built-in way to do so. You can just validate that the string is not null or empty:
function Test {
Param (
[ValidateNotNullOrEmpty()]
[String]
$String = 'DEFAULT'
)
Write-Host -Object "String: $String"
}
Use this inside your function:
If (!($var)) {$var = "DEFAULT"} #basically, if $var doesn't exist, create it
ps. I've just tested it with Mandatory = $false and it works as expected, no idea why it doesn't work for you, what does it do when mandatory = $false?

Resources