I'm writing a PowerShell function, and I want it to return a single Boolean. The problem is that PowerShell will return anything in the pipeline and not just what was explicitly included in the return statement.
Consider the following code.
function getTrue(){
$testVariable = "test"
$test
return $true
}
$answer = getTrue
Write-host $answer
The output of this code is
test True
This returns a System.Object[]. I want to force the return value to be a System.Boolean.
This code works fine if I add a Write-Host:
function getTrue(){
$testVariable = "test"
Write-Host $test
return $true
}
The only problem is that I'm depending on a function that someone else is writing (I can't modify it), and I can't gaurantee that they used a Write-Host instead of just leaving the variable on the line by itself.
Is there a way to force the function to only return the value that I want?
Just use Out-Null to suppress unexpected returning:
function getTrue(){
$test = "test";
$test | Out-Null;
return $true;
}
$answer = getTrue
Write-Host $answer
and
function getTrue(){
$test = "test";
Write-Host $test | Out-Null;
return $true;
}
$answer = getTrue
Write-Host $answer
Related
I have a utility function that specifically handles an exception and ignores it, but when testing it with Pester the test fails, showing the exception that was already caught and handled. Am I missing something, or is this a bug in Pester?
This code reproduces the issue:
function Test-FSPath {
[cmdletbinding()]
param([string]$FileSystemPath)
if([string]::IsNullOrWhiteSpace($FileSystemPath)) { return $false }
$result = $false
try {
if(Test-Path $FileSystemPath) {
Write-Debug "Verifying that $FileSystemPath is a file system path"
$item = Get-Item $FileSystemPath -ErrorAction Ignore
$result = ($item -ne $null) -and $($item.PSProvider.Name -eq 'FileSystem')
}
} catch {
# Path pattern that Test-Path / Get-Item can't handle
Write-Debug "Ignoring exception $($_.Exception.Message)"
}
return ($result -or ([System.IO.Directory]::Exists($FileSystemPath)) -or ([System.IO.File]::Exists($FileSystemPath)))
}
Describe 'Test' {
Context Test-FSPath {
It 'returns true for a path not supported by PowerShell Test-Path' {
$absPath = "$env:TEMP\temp-file[weird-chars.txt"
[System.IO.File]::WriteAllText($absPath, 'Hello world')
$result = Test-FSPath $absPath -Debug
$result | Should -Be $true
Write-Host "`$result = $result"
Remove-Item $absPath
}
}
}
Expected result: Test passes
Actual result: Test fails:
[-] returns true for a path not supported by PowerShell Test-Path 2.62s
WildcardPatternException: The specified wildcard character pattern is not valid: temp-file[weird-chars.txt
ParameterBindingException: Cannot retrieve the dynamic parameters for the cmdlet. The specified wildcard character pattern is not valid: temp-file[weird-chars.txt
The exception you're seeing is not coming from your function, its coming from your use of Remove-Item which is throwing the error trying to remove the erroneous path (which also doesn't exist). You should just remove it as you never expect the item to be created anyway.
Or, alternatively (as mentioned in the comments) use TestDrive: which you then don't need to worry about cleaning up (seems for the path to be supported you need to use $Testdrive).
It 'returns true for a path not supported by PowerShell Test-Path' {
$absPath = "$env:TEMP\temp-file[weird-chars.txt"
[System.IO.File]::WriteAllText($absPath, 'Hello world')
$result = Test-FSPath $absPath
$result | Should -Be $true
}
As an aside, I generally tend to do execution type stuff outside of the It, and just test the result inside. When I started to do this for your code it showed me that the test was passing, as the error then moved to occurring in the Context block. Here's what I mean (also this example makes use of TestDrive: via the $testdrive variable):
Describe 'Test' {
Context Test-FSPath {
$absPath = "$testdrive\temp-file[weird-chars.txt"
[System.IO.File]::WriteAllText($absPath, 'Hello world')
$result = Test-FSPath $absPath
It 'returns true for a path not supported by PowerShell Test-Path' {
$result | Should -Be $true
}
}
}
I need to write a function in powershell that tells apart a 'parameter not being passed' from one passed with string empty (or any other string)
I wrote it like this:
function Set-X {
param(
[AllowNull()][string]$MyParam = [System.Management.Automation.Language.NullString]::Value
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}
If I call Set-X without parameters from ISE, it works as I expect and prints 'ok'.
But if I do that from the normal console, it prints 'oops'.
What is going on? What is the proper way to do it?
Allowing the user to pass in a parameter argument value of $null does not change the fact that powershell will attempt to convert it to a [string].
Converting a $null value in powershell to a string results in an empty string:
$str = [string]$null
$null -eq $str # False
'' -eq $str # True
(same goes for $null -as [string] and "$null")
Remove the type constraint on the MyParam parameter if you not only want to allow $null but also accept $null as a parameter value:
function Set-X {
param(
[AllowNull()]$MyParam = [System.Management.Automation.Language.NullString]::Value
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}
As Mathias and BenH have written, the culprit is casting $null to the [string] type, which results in an empty string:
[string]$null -eq '' #This is True
But for the sample code in Mathias answer to work correctly we also have to replace
[System.Management.Automation.Language.NullString]::Value
with $null
function Set-X {
param(
[AllowNull()]$MyParam = $null
)
if ($null -ne $MyParam) { write-host 'oops' }
else { write-host 'ok' }
}
I'm writing a set of PowerShell scripts to monitor the size of various folders. I've run into an error, and I've got no idea what's causing it.
Here is the code, with Write-Host showing what I am expecting and what the variables $ip and $loc actually contain:
function getDriveLetter($ip) {
Write-Host $ip # prints: 192.168.10.10 myfolder1\myfolder2\
# expected: 192.168.10.10
switch($ip) {
"192.168.10.10" {return "E`$"; break}
"192.168.10.20" {return "D`$"; break}
default {"Unknown"; break}
}
}
function getFullPath($loc,$folder) {
Write-Host $loc # prints: 192.168.10.10 myfolder1\myfolder2\
# expected: 192.168.10.10
$drive = getDriveLetter("$loc")
$str = "\\$loc\$drive\DATA\$folder"
return $str
}
function testPath($loc,$folder) {
$mypath = getFullPath("$loc","$folder")
if (Test-Path $mypath) {
return $true
} else {
return $false
}
}
When I run the command:
testPath("192.168.10.10","myfolder1\myfolder2\")
I'm getting a "False" result, but if I run:
Test-Path "\\192.168.10.10\E`$\DATA\myfolder1\myfolder2\"
The command returns True (as it should).
What have I missed? I've tried forcing the variables to be set with:
$mypath = getFullPath -loc "$loc" -folder "$folder"
but there's no change. If it changes anything, this is on Powershell version 4.
I'd suggest that you review the syntax of PowerShell a bit more, because there's many mistakes in there. PowerShell is quite different from C# and you seem to make a lot of assumptions. :)
First of all, that's not how you call PowerShell functions. Also not sure why you added quotes around the parameters? They are redundant. If you fix the function calls your code should function as expected.
$mypath = getFullPath $loc $folder
Then there's a semicolon in your switch statement, which is also wrong. Then, you don't have to escape the $ if you just use ''. The break is also redundant because return exits the function in this case.
"192.168.10.10" { return 'E$' }
Also, one interesting thing about PowerShell: You could just get rid of the return in getFullPath:
function getFullPath($loc, $folder) {
$drive = getDriveLetter($loc)
"\\$loc\$drive\DATA\$folder"
}
PowerShell returns uncaptured output, which is important to be aware of, it can be the cause of many obscure bugs.
The problem is in how you are calling your functions. Function arguments are space delimited in PowerShell, and do not use parentheses to enclose the arguments.
getFullPath $loc $folder
When you wrap arguments in parentheses, you are creating an array containing two values, and passing that array as the first argument.
getFullPath($loc, $folder)
This line passes an array containing two strings #($loc, $folder) as the first argument, and then, because there are no other arguments on the line, it passes $null to the second. Inside the function, the array is then joined to be used as a string, which is the behavior you observed.
The problem is how you pass the parameters to the functions.
See more details on above link:
How do I pass multiple parameters into a function in PowerShell?
function getDriveLetter() {
param($ip)
switch($ip) {
"192.168.0.228" {return "E`$"; break}
"192.168.10.20" {return "D`$"; break}
default {"Unknown"; break}
}
}
function getFullPath() {
param($loc, $folder)
$drive = getDriveLetter -ip $loc
$str = "\\$loc\$drive\DATA\$folder"
return $str
}
function testPath() {
param($loc, $folder)
$mypath = getFullPath -loc $loc -folder $folder
if (Test-Path $mypath) {
return $true
} else {
return $false
}
}
testPath -loc "192.168.10.10" -param "myfolder1\myfolder2\"
The title says it all single boolean value becomes an array when assigned to a NoteProperty using Add-Member or using splatting.
PSVersion: 5.0.1xx
I have what I consider a strange problem. I am creating a PSObject with one of the NoteProperty members as a boolean. The function loops through a list, calls a function to perform an evaluation, creates an object and then adds it to an array. This seems to only happen to the first object created but I have not tested this with 5 or more objects being created.
I have validated that the functions are actually returning bool and that the variable being assigned to the property is an bool.
My workaround seems solid but am curious as to why this is happening.
Here's part of the code:
$clientCertsRequired = Get-Is-Client-Cert-Required -configFile $configFile -siteName $siteName
$httpsStatus = "Https is not enabled"
$clientCertStatus = "Client certs are not required"
if ($httpsEnabled -eq $true) {
$httpsStatus = "Https is enabled"
}
if ($clientCertsRequired -eq $true){
$clientCertStatus = "Client certs are required"
}
$sc = New-Object PSObject -Property #{
SiteName = $siteName;
ConfigFilePath = $path;
HttpsEnabled = $httpsStatus;
ClientCertStatus =$clientCertStatus;
ClientCertRequired = $clientCertsRequired;
}
# clean up of some inexplicable problem where assignment to property
# produces array with actual value in the last element.
if ($sc.ClientCertRequired.GetType().Name -eq "Object[]"){
$sc.ClientCertRequired = $sc.ClientCertRequired[-1]
}
$si += $sc
Function Get-Is-Client-Cert-Required{
param(
[xml]$configFile,
[string]$siteName
)
$functionName = $MyInvocation.MyCommand.Name
$clientCertRequired = $false
try{
# then read locations section (this will often not have any pages
$locationPath = "//configuration/location[#path='$siteName']"
[system.xml.xmlelement]$location = $configFile.DocumentElement.SelectSingleNode($locationPath)
if($location -ne $null){
[system.xml.xmlelement]$accessNode = $location.SelectSingleNode("system.webServer/security/access")
[system.xml.xmlelement]$authenticationNode = $location.SelectSingleNode("system.webServer/security/authentication")
[system.xml.xmlelement]$clientCertMappingNode
[system.xml.xmlelement]$iisClientCertMappingNode
[int]$sslFlagMask = 0
if($accessNode -ne $null){
$sslFlags = $accessNode.Attributes.GetNamedItem("sslFlags")
# $sslFlags = $accessNode.Attributes["sslFlags"].Value
if($sslFlagMask -ne $null){
$sslFlagMask = Convert-Ssl-Flag-String-To-Int-Flag -sslFlag $sslFlags.Value
}
}
if($authenticationNode -ne $null){
[system.xml.xmlelement]$clientCertMappingNode = $authenticationNode.SelectSingleNode("clientCertificateMappingAuthentication[#enabled='true']")
[system.xml.xmlelement]$iisClientCertMappingNode = $authenticationNode.SelectSingleNode("iisClientCertificateMappingAuthentication[#enabled='true']")
}
$clientCertAccepted = ($sslFlagMask -band $certAccepted) -eq $certAccepted
$clientCertRequired = Check-IIS-Express-SSL-Config $sslFlagMask
if($clientCertRequired -eq $false){
if($clientCertAccepted -and ($clientCertMappingNode -ne $null -or $iisClientCertMappingNode -ne $null)){
$clientCertRequired = $true
}
}
}
}catch{
$exceptionMessage = Get-Formatted-Exception-String -exceptionObject $_
$message = "$functionName - Exception`: $exceptionMessage"
Add-Exception -exception $message
Log-Error -message $message
}
$clientCertRequired
}
In the body of the Get-Is-Client-Cert-Required function, you do:
[system.xml.xmlelement]$clientCertMappingNode
[system.xml.xmlelement]$iisClientCertMappingNode
This pattern:
[type]$nonExistingVariable
Is a terrible idea in PowerShell - unlike C#, PowerShell does not have the concept of bare variable declarations, and the above pattern simply casts $null to the specified type, emitting a new instance of said type if it succeeds - this is likely what causes the function to output an array.
If you really need to bind a variable to a specific type, cast on assignment:
[type]$Variable = Get-Stuff
Bonus tip: The PowerShell-idiomatic naming convention for functions and cmdlets is Noun-Verb, with only a single hyphen. A more appropriate name for the function would be:
Test-ClientCertRequirement
I have the following function that shows if any of the words in an array are contained in a string:
function AnyOf ([string] $line, [string[]] $list)
{
Write-Output "AnyOf!"
foreach($item in $list)
{
if($line.Contains($item))
{
Write-Output "Found: $item"
return $true
}
}
return $false
}
And this to test it:
function main
{
[string[]]$a = #("user1", "user2")
$str = "user1 and user2"
$res = AnyOf($str, $a)
}
I expect it to see both user1 and user2 to get printed out but nothing is printed. It appears as though the function isn't getting called because the Write-Output "AnyOf!" is never executed.
However, when I execute:
AnyOf($str, $a)
Instead of:
$res = AnyOf($str, $a)
I can see that the function is called but the iteration doesn't seem to happen... What am I doing wrong?
A few things,
First of all, functions in powershell are not called using () you would call the function as
AnyOf $str $a
The standard convention is $<<nameOfFunction>> <<param1>> <<param2>>
Secondly, your output is being captured in $res and therefore not printed to the screen, if you add $res at the end of your main function it will output.
So your main function becomes:
function main
{
[string[]]$a = #("user1", "user2")
$str = "user1 and user2"
$res = AnyOf $str $a
$res #or just don't assign it to $res..
}
#to call main:
main
Output:
AnyOf!
Found: user1
True
As you can see in the above output it never finds User2 because you use a return $true once it finds a match, exiting out of the function.
Another thing to note is that when you return $false or $true these actually become part of the output stream, which might not be what you want as it can lead to unexpected behavior.