I'm trying to run a function that parses a large string that contains newlines, however whenever I pass this string into a function it gets rid of the new lines and makes it impossible to parse. Am I doing something wrong here?
function parseString([string] $s)
{
$result = $s | Select-String -pattern "foo"
return $result
}
If I type:
$s | Select-String -pattern "foo"
I get the correct result but using
parseString $s
returns the whole string with no newlines. Any suggestions?
EDIT: Hmm after messing around a bit I got rid of the [string] so it's
function parseString($s)
This seems to work, but why?
What is $s? If it is an array of string, then since you are saying that parseString takes in a string, the array of string is converted into a string. If on the other hand $s were a single string, it will work ( as shown below):
function parseString([string] $s)
{
$result = $s | Select-String -pattern "foo"
return $result
}
$s =#'
first line
second line with foo
third line
'#
parseString $s
But if $s=#("first line","secondline with foo","third line"), the array of strings is first converted to a string ( by simply joining each string ) and hence you will lose the newline. If you have got $s, from Get-Content etc. this will be the case.
Note that, most of the times, you won't need to specify the types in Powershell. Be it while assigning variables or in function paramaters.
PS:
If you did
$ofs = "`n"
parseString $s
you will get the expected result in the function with [string].
Removing [string] is probably removing the cast that is happening. You can cast object types to other types this way. For example:
$thing = [int] 42
$thing.GetType().FullName
Output: System.Int32
$thing = [string] $thing
$thing.GetType().FullName
Output: System.String
If your input is a string array, [string] will cast it to a single string, like the -join operator.
To see what is happing, print out $YourVariable.GetType().FullName before calling the function and inside the function (still using [string]).
Related
Good evening. I'm totally newbie to powershell and I have surely a silly question but I can't find an answer on my own.
I have a txt file like this
192.168.1.1|2
192.168.1.2|4
192.168.1.3|3
My function takes an IP as a parameter and it returns the integer values after the pipe. The function works but I don't know how to sum a value to the function result.
$client = "192.168.1.2"
function file-update($client) {
$clientrow = gc "C:\clients.txt" | ? {$_ -match $client}
if ($clientrow) {
$filesupdated = $clientrow.Split("|")[1]
return $filesupdated
}
else {
return 0
}
}
file-update $client
# it returns 4
file-update $client + 1
# it returns 4 too instead of 5
What'is my mistake?
Thanks in advance.
You need your function to execute and return a value before performing the addition. You can simply use () to group the function call. Since your function returns a [string] when a client is found, you will have to do a conversion to a numeric type to support addition. Having an integer on the left-hand side (LHS) of the operator (+) will convert the RHS value to [int] automatically if possible.
1 + (file-update $client)
You can write the function differently to minimize the amount of work done to extract the integer value:
# Best practice is to use verb-noun for naming functions
# Added file parameter (for file path) to not hard code it inside the function
function Update-File {
Param(
$client,
$file
)
# Casting a null value to [int] returns 0
# Delimiter | to override default ,
# Named headers are needed since the file lacks them
[int](Import-Csv $file -Delimiter '|' -Header IP,Number |
Where IP -eq $client).Number
}
$client = '192.168.1.2'
$file = 'c:\clients.txt'
Update-File $client $file # returns 4
(Update-File $client $file) + 1 # returns 5
Passing switch parameter thru pipeline in PowerShell
Problem
I am trying to make a function that has a switch parameter, but also I want to able to pass all function parameters thru pipeline in a script, and I don't know ho to do that. Is it that even possible? I my case I load parameters from .csv file in witch values are string values.
Exposition
To simplify my problem and to make it easier for others to use answers of this question, I am not going to use my code but an abstract version of my code. Let us call my function New-Function that has a -StringParameter, a -IntParameter and a -SwitchParameter parameters. And just to be clear in my .csv file all fields are named same as the New-Function parameters.
Using the function
Normally I you can use the New-Function this way:
New-Function -StringParameter "value" -IntParameter 123 -SwitchParameter
But I also want to use the New-Function this way:
$Data = Import-Csv -Path "$PSScriptRoot\Data.csv" -Delimiter ';'
$Data | New-Function
My attempts
I have tried to convert the string values in pipe line to boolean but it seems like the function's -SwitchParameter does not accept boolean($true, $false) values, because it skipping the process block completely when I debug it.
$Data | ForEach-Object -Process {
if ($_.SwitchParameter -eq "true") {
$_.SwitchParameter = $true
}
else {
$_.SwitchParameter = $false
}
} | New-Function
My temporary workaround
I have settled to use a string parameter instead of a switch parameter, so I can feed the New-Function with data thru pipeline from a .csv file with no problem.
function New-Function {
param (
[Parameter(Position = 0, Mandatory, ValueFromPipelineByPropertyName)]
[string]
$StringParameter,
[Parameter(Position = 1, Mandatory, ValueFromPipelineByPropertyName)]
[int]
$IntParameter,
[Parameter(Position = 2, ValueFromPipelineByPropertyName)]
[string]
$SwitchParameter = "false"
)
#----------------------------------------------------------------------------
}
You have to convert values for switch parameter to boolean type.
It works to me:
function Out-Test
{
param
(
[Parameter(ValueFromPipelineByPropertyName)]
[String]
$Label,
[Parameter(ValueFromPipelineByPropertyName)]
[Switch]
$Show
)
process
{
$Color = if ($Show) { 'Yellow' } else { 'Gray' }
Write-Host -ForegroundColor $Color $Label
}
}
$row1 = '' | select Label, Show
$row1.Label = 'First'
$row1.Show = 'True'
$row2 = '' | select Label, Show
$row2.Label = 'Second'
$row1.Show = 'False'
$rows = $row1, $row2
$rows |% { $_.Show = [bool]$_.Show }
$rows | Out-Test
Result:
You can convert your string to a Boolean object while leaving your parameter as type [switch] in your function. The Boolean type will be coerced into [switch] during binding.
$Data | Foreach-Object {
$_.SwitchParameter = [boolean]::Parse($_.SwitchParameter)
$_
} | New-Function
Alternatively, you can update all of your objects first and then pipe to your function. It matters how your function handles the input objects.
$Data | Foreach-Object {
$_.SwitchParameter = [boolean]::Parse($_.SwitchParameter)
}
$Data | New-Function
Part of the issue with your Foreach-Object attempt is that you never output the updated object $_ before piping into your function.
I am not able to twist my head into understanding how to get Powershell to loop the entire JSON Structure, it wont' loop the System.Object[]
$x = ConvertFrom-Json '{
"Car companies": {
"Name of Company": "Ford",
"Cars": [{
"Name of car": "Ranger",
"Config": "Pickup"
},
{
"Name of car": "Puma",
"Config": "Hatchback"
}]
}
}'
foreach( $rootProperty in #($x.psobject.properties | where-object {$_.MemberType -eq "NoteProperty"}) ) {
write-host " - '$($rootProperty.Name)' = '$($rootProperty.Value)'"
foreach( $childProperty in #($rootProperty.Value.psobject.properties ) ) {
write-host "'$($childProperty.Name)' = '$($childProperty.Value)'"
}
}
Outut I get now is just
- 'Brand' = '#{Name of Brand=Ford; Cars=System.Object[]}'
Name of Brand' = 'Ford'
Cars' = ' '
...as a follop How to iterate through a unknown JSON data/object?
tl;dr
You're seeing a bug that unexpectedly string-expands the Cars property value's array elements to the empty string.
A simple workaround - for display purposes only - is to pipe the property value to Out-String to get the usual display representation:
"'$($childProperty.Name)' = '$($childProperty.Value | Out-String)'"
You're seeing a bug in how arrays of [pscustomobject] instances are stringified (as of PowerShell Core 7.0.0-preview.6):
Generally, PowerShell arrays are stringified by joining the stringified element representations with the separator specified in the $OFS preference variable, which defaults to a space char.
Normally, [pscustomobject] instances have a string representation that resembles a hashtable literal (but isn't one); e.g.:
PS> $custObj = [pscustomobject] #{ foo = 'bar' }; "$custObj"
#{foo=bar} # string representation that *resembles* a hashtable literal
Unexpectedly - and this is the bug - when custom objects are the elements of an array, they stringify to the empty string, which is what you saw:
PS> $custObj = [pscustomobject] #{ foo = 'bar' }; $arr = $custObj, $custObj; "[$arr]"
[ ] # !! Bug: custom objects stringified to empty strings, joined with a space
This is an indirect manifestation of a long-standing bug reported in this GitHub issue: that is, elements of an array being stringified are stringified by calls to their .ToString() method, and calling .ToString() on custom objects unexpectedly yields the empty string (unlike the string representation you get when you directly reference a single custom object in an expandable string, as shown above).
I am really new to Powershell but I have programmed in Java and other languages.
I am trying to pass a string to a function and then have that function return the string. Below is the simple code I am trying to run:
#funcpass.ps1
function test {
Param([string]$input)
$out = $input
return $out
}
$a = test hello world
Write-Host $a
I expect this to pass the string hello world then return the string into the variable $a to be printed. Instead my console returns this:
PS P:\test> .\funcpass.ps1
PS P:\test>
Is there some kind of scope error that I am encountering? Any help would be greatly appreciated. I am not sure if the version number helps, but here it is:
PS P:\test> $PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 14393 1198
The $input parameter is reserved, so you have to change its name. Moreover, if you want to pass only one string to the function you have to enclose it with quotes:
function Run-Test {
Param([string]$inputValue)
$out = $inputValue
return $out
}
$a = Run-Test "hello world"
Write-Host $a
FYI the return keyword is optional but it makes your intentions more clear as other language use return to indicate that something is being returned from the function. Everything sent on the pipeline inside the function (like Write-Output) will be returned.
Here is a short example (my actual code requires me to output many more tables during the function run, and get a single returned output from it):
Function Select-RowFromCSV ($CSV)
{
$CSV
return $CSV[(read-host "select row # from CSV")]
}
Instead of outputting $CSV within the function it gets appended to the return and is getting into the variable that the function inserted to.
PS C:\Windows\system32> $ROW = Select-RowFromCSV -CSV (Import-Csv "C:\scripts\csv.csv")
select row # from CSV: 0
PS C:\Windows\system32> $ROW
Name Phone
Dan 111111
Dave 5555555
Oliver 666666
Dan 111111
PS C:\Windows\system32>
I tried multiple ways to try and print it to the screen, however unlike write-host that do work as expected for strings, none of the other one i tried works for non strings objects (FT, write-output, echo).
If you want to output something to the console without affecting the output of the Function, Write-Host is probably the simplest solution (but is considered harmful). Your code needs to be as follows:
Function Select-RowFromCSV ($CSV)
{
Write-Host ($CSV | Format-Table | Out-String)
Return $CSV[(read-host "select row # from CSV")]
}
$ROW = Select-RowFromCSV -CSV (Import-Csv "raid.csv")
As you observed, because your object isn't a string you need to Format it as you'd like and then convert it to a String (| Format-Table | Out-String).
However, you might want to consider using Write-Verbose which will write output to the Verbose stream instead, only when the -Verbose switch is used. To use this you need to add [cmdletbinding()] and a Param() block to your function, like this:
Function Select-RowFromCSV
{
[cmdletbinding()]
Param($CSV)
Write-Verbose ($CSV | Format-Table | Out-String)
Return $CSV[(read-host "select row # from CSV")-1]
}
Then execute your function with the -Verbose switch to see the extra output:
$ROW = Select-RowFromCSV -CSV (Import-Csv "raid.csv") -Verbose
Further Explanation:
In your original function you were outputting all of $CSV because it appeared on a line by itself, then also returning a row of it. The Return keyword is a little misleading in PowerShell, it doesn't define what is only returned, it just triggers the Function to immediately end and anything that has been output will go in to the output stream.
You should also note that the row number your user enters needs to start from 0, because Array indexes start from 0.
If you wanted the first row to be 1 rather than 0, you might want to do this:
$CSV[(read-host "select row # from CSV")-1]
You could also drop the Return keyword entirely as it's not necessary. If you want to be more explicit, I personally favour Write-Output.
Just use the following method:
$counter = 0
$table = #()
XXX | foreach-object {
do something;
$table +=[pscustomobject]# {
XXXX
}
$table[$counter]
$counter++
}