Powershell function being passed too many parameters - powershell

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\"

Related

Where Command not Working? [duplicate]

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

Powershell loop only if condition is true

Very new to coding in general, so I fear I am missing something completely obvious. I want my program to check for a file. If it is there, just continue the code. If it has not arrived, continue cheking for a given amount of time, or untill the file shows up. My loop works on its own, so when i only select the do-part in Powershell ISE, it works. But when i try running it inside the if statement, nothing happens. The loops doesnt begin.
$exists= Test-Path $resultFile
$a = 1
if ($exists -eq "False")
{
do
{
$a++
log "Now `$a is $a "
start-sleep -s ($a)
$exists= Test-Path $resultFile
write-host "exists = $exists"
}
while (($a -le 5) -and ($exists -ne "True"))
}
Another way of doing this is using a while loop:
$VerbosePreference = 'Continue'
$file = 'S:\myFile.txt'
$maxRetries = 5; $retryCount = 0; $completed = $false
while (-not $completed) {
if (Test-Path -LiteralPath $file) {
Write-Verbose "File '$file' found"
$completed = $true
# Do actions with your file here
}
else {
if ($retryCount -ge $maxRetries) {
throw "Failed finding the file within '$maxRetries' retries"
} else {
Write-Verbose "File not found, retrying in 5 seconds."
Start-Sleep '5'
$retryCount++
}
}
}
Some tips:
Try to avoid Write-Host as it kills puppies and the pipeline (Don Jones). Better would be, if it's meant for viewing the script's progress, to use Write-Verbose.
Try to be consistent in spacing. The longer and more complex your scripts become, the more difficult it will be to read and understand them. Especially when others need to help you. For this reason, proper spacing helps all of us.
Try to use Tab completion in the PowerShell ISE. When you type start and press the TAB-key, it will automatically propose the options available. When you select what you want with the arrow down/up and press enter, it will nicely format the CmdLet to Start-Sleep.
The most important tip of all: keep exploring! The more you try and play with PowerShell, the better you'll get at it.
As pointed out in comments, your problem is that you're comparing a boolean value with the string "False":
$exists -eq "False"
In PowerShell, comparison operators evaluate arguments from left-to-right, and the type of the left-hand argument determines the type of comparison being made.
Since the left-hand argument ($exists) has the type [bool] (a boolean value, it can be $true or $false), PowerShell tries to convert the right-hand argument to a [bool] as well.
PowerShell interprets any non-empty string as $true, so the statement:
$exists -eq "False"
is equivalent to
$exists -eq $true
Which is probably not what you intended.

Debugging PowerShell

I'm not certain what is wrong with this scriptlet.
I'm trying to break out functionality into several other functions (I have a programming background not a scripting one per se) and to me LOGICALLY the following should execute starting at the "main" function Test-SgnedMpsPackage, accepting the various optional parameters (the script is not yet complete) then when the function Check-Path is called, that is run, then work would resume in the original calling function.
Am I missing something here?
On a side note, how does one return a value to the calling function? a simple return?
function CheckPath($path)
{
if ( test-path -Path $path )
{ Write-Host "{0} confirmed to exist." -f $path }
else
{ Write-Host "{0} DOES NOT exis.\nPlease check and run the script again" -f $path }
exit { exit }
}
function Test-SignedMpsPackage
{
Param(
[string] $PkgSource,
[string] $SigSource,
[string] $Destination
)
Process
{
#Check that both files exist
Write-Host "Check for file existence..."
CheckPath($PkgSource)
CheckPath($SigSource)
#retrieve signatures from file
}
}
Unlike C, C++ or C# there is no "main" entry point function. Any script at the top level - outside of a function - executes. You have defined two functions above but you haven't called either one. You need to do something like this:
function Test-SignedMpsPackage
{
...
}
Test-SignedMpsPackage params
Also as mentioned by #Bill_Stewart, you call your defined functions just like you call PowerShell commands - arguments are space separated and you don't use parens except to evaluate an expression inside the parens.
As for returning a value from a function, any output (Output stream) not captured by assigning to a variable or being redirected to a file is automatically part of the function's output. So I would modify your CheckPath function to this:
function CheckPath($path)
{
if (Test-Path -Path $path) {
Write-Verbose "{0} confirmed to exist." -f $path
$true
}
else {
Write-Verbose "{0} DOES NOT exist.\nPlease check and run the script again" -f $path
$false
}
}
You can use Write-Host as you had before but sometimes, perhaps in a script, you don't want to see the extra output. That is where Write-Verbose comes in handy. Set $VerbosePreference = 'Continue' to see the verbose output.

How to return the name of the calling script from a Powershell Module?

I have two Powershell files, a module and a script that calls the module.
Module: test.psm1
Function Get-Info {
$MyInvocation.MyCommand.Name
}
Script: myTest.ps1
Import-Module C:\Users\moomin\Documents\test.psm1 -force
Get-Info
When I run ./myTest.ps1 I get
Get-Info
I want to return the name of the calling script (test.ps1). How can I do that?
Use PSCommandPath instead in your module:
Example test.psm1
function Get-Info{
$MyInvocation.PSCommandPath
}
Example myTest.ps1
Import-Module C:\Users\moomin\Documents\test.psm1 -force
Get-Info
Output:
C:\Users\moomin\Documents\myTest.ps1
If you want only the name of the script that could be managed by doing
GCI $MyInvocation.PSCommandPath | Select -Expand Name
That would output:
myTest.ps1
I believe you could use the Get-PSCallStack cmdlet, which returns an array of stack frame objects. You can use this to identify the calling script down to the line of code.
Module: test.psm1
Function Get-Info {
$callstack = Get-PSCallStack
$callstack[1].Location
}
Output:
myTest.ps1: Line 2
Using the $MyInvocation.MyCommand is relative to it's scope.
A simple example (Of a script located : C:\Dev\Test-Script.ps1):
$name = $MyInvocation.MyCommand.Name;
$path = $MyInvocation.MyCommand.Path;
function Get-Invocation(){
$path = $MyInvocation.MyCommand.Path;
$cmd = $MyInvocation.MyCommand.Name;
write-host "Command : $cmd - Path : $path";
}
write-host "Command : $cmd - Path : $path";
Get-Invocation;
The output when running .\c:\Dev\Test-Script.ps1 :
Command : C:\Dev\Test-Script.ps1 - Path : C:\Dev\Test-Script.ps1
Command : Get-Invocation - Path :
As you see, the $MyInvocation is relative to the scoping. If you want the path of your script, do not enclose it in a function. If you want the invocation of the command, then you wrap it.
You could also use the callstack as suggested, but be aware of scoping rules.
I used this today after trying a couple of techniques.
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
$ScriptName = $MyInvocation.MyCommand | select -ExpandProperty Name
Invoke-Expression ". $Script\$ScriptName"
To refer to the invocation info of the calling script, use:
#(Get-PSCallStack)[1].InvocationInfo
e.g.:
#(Get-PSCallStack)[1].InvocationInfo.MyCommand.Name
This provides the script path with trailing backslash as one variable and the script name as another.
The path works with Powershell 2.0 and 3.0 and 4.0 and probably 5.0
Where with Posershell $PSscriptroot is now available.
$_INST = $myinvocation.mycommand.path.substring(0,($myinvocation.mycommand.path.length - $MyInvocation.mycommand.name.length))
$_ScriptName = $myinvocation.mycommand.path.substring($MyInvocation.MyCommand.Definition.LastIndexOf('\'),($MyInvocation.mycommand.name.length +1))
$_ScriptName = $_ScriptName.TrimStart('\')
If you want a more reusable approach, you can use:
function Get-CallingFileName
{
$cStack = #(Get-PSCallStack)
$cStack[$cStack.Length-1].InvocationInfo.MyCommand.Name
}
The challenge I had was having a function that could be reused within the module. Everything else assumed that the script was calling the module function directly and if it was removed even 1 step, then the result would be the module file name. If, however, the source script is calling a function in the module which is, in turn, calling another function in the module, then this is the only answer I've seen that can ensure you're getting the source script info.
Of course, this approach is based on what #iRon and #James posted.
For you googlers looking for quick copy paste solution,
here is what works in Powershell 5.1
Inside your module:
$Script = (Get-PSCallStack)[2].Command
This will output just the script name (ScriptName.ps1) which invoked a function located in module.
I use this in my module:
function Get-ScriptPath {
[CmdletBinding()]
param (
[string]
$Extension = '.ps1'
)
# Allow module to inherit '-Verbose' flag.
if (($PSCmdlet) -and (-not $PSBoundParameters.ContainsKey('Verbose'))) {
$VerbosePreference = $PSCmdlet.GetVariableValue('VerbosePreference')
}
# Allow module to inherit '-Debug' flag.
if (($PSCmdlet) -and (-not $PSBoundParameters.ContainsKey('Debug'))) {
$DebugPreference = $PSCmdlet.GetVariableValue('DebugPreference')
}
$callstack = Get-PSCallStack
$i = 0
$max = 100
while ($true) {
if (!$callstack[$i]) {
Write-Verbose "Cannot detect callstack frame '$i' in 'Get-ScriptPath'."
return $null
}
$path = $callstack[$i].ScriptName
if ($path) {
Write-Verbose "Callstack frame '$i': '$path'."
$ext = [IO.Path]::GetExtension($path)
if (($ext) -and $ext -eq $Extension) {
return $path
}
}
$i++
if ($i -gt $max) {
Write-Verbose "Exceeded the maximum of '$max' callstack frames in 'Get-ScriptPath'."
return $null
}
}
return $null
}
You can grab the automatic variable MyInvocation from the parent scope and get the name from there.
Get-Variable -Scope:1 -Name:MyInvocation -ValueOnly
I did a basic test to check to see if it would always just get the direct parent scope and it worked like a treat and is extremely fast as opposed to Get-PSCallStack
function ScopeTest () {
Write-Information -Message:'ScopeTest'
}
Write-nLog -Message:'nLog' -Type:110 -SetLevel:Verbose
ScopeTest

Function return value in PowerShell

I have developed a PowerShell function that performs a number of actions involving provisioning SharePoint Team sites. Ultimately, I want the function to return the URL of the provisioned site as a String so at the end of my function I have the following code:
$rs = $url.ToString();
return $rs;
The code that calls this function looks like:
$returnURL = MyFunction -param 1 ...
So I am expecting a String, however it's not. Instead, it is an object of type System.Management.Automation.PSMethod. Why is it returning that type instead of a String type?
PowerShell has really wacky return semantics - at least when viewed from a more traditional programming perspective. There are two main ideas to wrap your head around:
All output is captured, and returned
The return keyword really just indicates a logical exit point
Thus, the following two script blocks will do effectively the exact same thing:
$a = "Hello, World"
return $a
 
$a = "Hello, World"
$a
return
The $a variable in the second example is left as output on the pipeline and, as mentioned, all output is returned. In fact, in the second example you could omit the return entirely and you would get the same behavior (the return would be implied as the function naturally completes and exits).
Without more of your function definition I can't say why you are getting a PSMethod object. My guess is that you probably have something a few lines up that is not being captured and is being placed on the output pipeline.
It is also worth noting that you probably don't need those semicolons - unless you are nesting multiple expressions on a single line.
You can read more about the return semantics on the about_Return page on TechNet, or by invoking the help return command from PowerShell itself.
This part of PowerShell is probably the most stupid aspect. Any extraneous output generated during a function will pollute the result. Sometimes there isn't any output, and then under some conditions there is some other unplanned output, in addition to your planned return value.
So, I remove the assignment from the original function call, so the output ends up on the screen, and then step through until something I didn't plan for pops out in the debugger window (using the PowerShell ISE).
Even things like reserving variables in outer scopes cause output, like [boolean]$isEnabled which will annoyingly spit a False out unless you make it [boolean]$isEnabled = $false.
Another good one is $someCollection.Add("thing") which spits out the new collection count.
With PowerShell 5 we now have the ability to create classes. Change your function into a class, and return will only return the object immediately preceding it. Here is a real simple example.
class test_class {
[int]return_what() {
Write-Output "Hello, World!"
return 808979
}
}
$tc = New-Object -TypeName test_class
$tc.return_what()
If this was a function the expected output would be
Hello World
808979
but as a class the only thing returned is the integer 808979. A class is sort of like a guarantee that it will only return the type declared or void.
As a workaround I've been returning the last object in the array that you get back from the function... It is not a great solution, but it's better than nothing:
someFunction {
$a = "hello"
"Function is running"
return $a
}
$b = someFunction
$b = $b[($b.count - 1)] # Or
$b = $b[-1] # Simpler
All in all, a more one-lineish way of writing the same thing could be:
$b = (someFunction $someParameter $andAnotherOne)[-1]
I pass around a simple Hashtable object with a single result member to avoid the return craziness as I also want to output to the console. It acts through pass by reference.
function sample-loop($returnObj) {
for($i = 0; $i -lt 10; $i++) {
Write-Host "loop counter: $i"
$returnObj.result++
}
}
function main-sample() {
$countObj = #{ result = 0 }
sample-loop -returnObj $countObj
Write-Host "_____________"
Write-Host "Total = " ($countObj.result)
}
main-sample
You can see real example usage at my GitHub project unpackTunes.
The existing answers are correct, but sometimes you aren't actually returning something explicitly with a Write-Output or a return, yet there is some mystery value in the function results. This could be the output of a builtin function like New-Item
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
Directory: C:\temp
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2/9/2020 4:32 PM 132257575335253136
True
All that extra noise of the directory creation is being collected and emitted in the output. The easy way to mitigate this is to add | Out-Null to the end of the New-Item statement, or you can assign the result to a variable and just not use that variable. It would look like this...
PS C:\temp> function ContrivedFolderMakerFunction {
>> $folderName = [DateTime]::Now.ToFileTime()
>> $folderPath = Join-Path -Path . -ChildPath $folderName
>> New-Item -Path $folderPath -ItemType Directory | Out-Null
>> # -or-
>> $throwaway = New-Item -Path $folderPath -ItemType Directory
>> return $true
>> }
PS C:\temp> $result = ContrivedFolderMakerFunction
PS C:\temp> $result
True
New-Item is probably the more famous of these, but others include all of the StringBuilder.Append*() methods, as well as the SqlDataAdapter.Fill() method.
You need to clear output before returning. Try using Out-Null. That's how powershell return works. It returns not the variable you wanted, but output of your whole function. So your example would be:
function Return-Url
{
param([string] $url)
. {
$rs = $url.ToString();
return
} | Out-Null
return $rs
}
$result = Return-Url -url "https://stackoverflow.com/questions/10286164/function-return-value-in-powershell"
Write-Host $result
Write-Host $result.GetType()
And result is:
https://stackoverflow.com/questions/10286164/function-return-value-in-powershell
System.String
Credits to https://riptutorial.com/powershell/example/27037/how-to-work-with-functions-returns
It's hard to say without looking at at code. Make sure your function doesn't return more than one object and that you capture any results made from other calls. What do you get for:
#($returnURL).count
Anyway, two suggestions:
Cast the object to string:
...
return [string]$rs
Or just enclose it in double quotes, same as above but shorter to type:
...
return "$rs"
Luke's description of the function results in these scenarios seems to be right on. I only wish to understand the root cause and the PowerShell product team would do something about the behavior. It seems to be quite common and has cost me too much debugging time.
To get around this issue I've been using global variables rather than returning and using the value from the function call.
Here's another question on the use of global variables:
Setting a global PowerShell variable from a function where the global variable name is a variable passed to the function
The following simply returns 4 as an answer. When you replace the add expressions for strings it returns the first string.
Function StartingMain {
$a = 1 + 3
$b = 2 + 5
$c = 3 + 7
Return $a
}
Function StartingEnd($b) {
Write-Host $b
}
StartingEnd(StartingMain)
This can also be done for an array. The example below will return "Text 2"
Function StartingMain {
$a = ,#("Text 1","Text 2","Text 3")
Return $a
}
Function StartingEnd($b) {
Write-Host $b[1]
}
StartingEnd(StartingMain)
Note that you have to call the function below the function itself. Otherwise, the first time it runs it will return an error that it doesn't know what "StartingMain" is.