I am trying to write a function that echo's the input if my script is running in debug mode.
[bool]$debugmode = $true
#one liner version for manually pasting into the powershell console
#Function DebugWrite-Output([bool]$isDebug,$inputObject){if ($isDebug -eq $true){Write-Output $inputObject}}
Function DebugWrite-Output([bool]$isDebug,$inputObject)
{if ($isDebug -eq $true){
Write-Output $inputObject
}
}
DebugWrite-Output -isDebug = $debugmode -inputObject "Loading create access file function"
the error i get is
DebugWrite-Output : Cannot process argument transformation on parameter 'isDebug'. Cannot convert value "System.String" to type "System.Boolean". Boolean parameters accept only Boolean values and numbers, such as
$True, $False, 1 or 0.
At C:\Users\*****\source\repos\Powershell Scripts\Modular-Export.ps1:9 char:28
+ DebugWrite-Output -isDebug = $debugmode -inputObject "Loading create ...
+ ~
+ CategoryInfo : InvalidData: (:) [DebugWrite-Output], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,DebugWrite-Output
This error doesnt make sense to me since i am passing a boolean into the boolean and a string into the psobject.
You have two options
Correct your syntax error: You must pass parameters in the form of -param [value]:
-isDebug $debugmode
Use the right tool for the job, a [switch] parameter:
function DebugWrite-Output([switch] $isDebug, $inputObject) {
if ($isDebug.IsPresent) { ...
Then you call it by just including the switch:
-isDebug
Related
I have a function mainFunction that gets 2 parameters - $name will be just a regular string, and $moveFunction will be some function.
I want to start a job of a ScriptBlock ($SB) that will invoke $moveFunction with $name as his argument.
function foo($a){
Write-Output "In function foo with the argument => $a"
}
$SB = {
param($C, $fooFunction)
$fooFunction.Invoke($C)
}
function mainFunction($name, $moveFunction){
Start-Job -Name "currentJob" -ArgumentList $name, ${Function:$moveFunction} -ScriptBlock $SB
}
$j1 = mainFunction -name "output!" -moveFunction $Function:foo
I checked that $moveFunction exists in mainFunction already ($moveFunction.invoke(5) at mainFunction)
I can't find the problem in passing the function as argument in the start-job.
and from Get-Job -Name "CurrentJob" | Receive-Job I get:
You cannot call a method on a null-valued expression.
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
+ PSComputerName : localhost
Any help would be appreciated.
edit:
The problem is most likely the way I pass the function as an argument (${Function:$moveFunction}
Just a rehash of my previous comment plus code example. Similar issue here. Essentially, arguments passed to Jobs and Remote commands are serialized. During the de-serialization process, functions and script blocks come out as strings instead of their original type. Fortunately it's a simple process to transform these into invokable scriptblocks using [ScriptBlock]::Create("string").
function foo {
write-host "foo"
}
function bar {
# This argument comes in as a string
param($func)
write-host "bar"
# Create scriptblock from string
$func = [ScriptBlock]::Create($func)
$func.invoke()
}
Start-Job -ArgumentList $Function:Foo -ScriptBlock $Function:Bar
Get-Job | Wait-job
Get-Job | Receive-job
You passing the same function and invoking it. You can directly use the function in the job.
Start-Job -Name "currentJob" -ArgumentList $name - ScriptBlock ${function:foo}
It seems that PowerShell adds an additional variable to the return value of a function.
The function subfoo2 itself delivers the correct values, but as soon as PowerShell jumps back to the postion where I called the function (in foo1), value contains the value of an other variable ($msg)
(Have a look at the comments in the code)
writeMessageLog($msg){
...
Add-Content $msg
...
}
subfoo2{
writeMessageLog($msg)
return $UserArrayWithValues #During Debug, $Array is fine (1)
}
foo1{
$var = subfoo2 $UserArray # $var has now the value of $msg and $UserArrayWithValues (2)
#do something with var
}
Realcode:
function WriteLog
{
param ( [string] $severity , $msgNumber, [string] $msg )
...
$msgOut = $date + ... + $msg
Add-Content $msgout ( $msgOut )
...
}
function getFeatures
{
writelog 'I' 1002 $true $true "Load Features"
$Features = importCsv -pPath $FeatureDefintionFilePath
Writelog 'I' 1000 $true $true "Features Loaded"
return $Features # $Features has value as expected (1)
}
function GetUserFeatures ($pUserObject)
{
$SfBFeatures = ""
$SfBFeatures = getFeatures #SfBFeaures has Value of $msg and $Features (2)
...
}
Do I use the functions/return values wrong? What could lead to such behavior? Is it an issue if i call a function within a function?
If I remove $msgOut = $date + ... + $msg in writeMessageLog, the values are fine.
I'm pretty lost right now, and have no ideas where this comes from. Any ideas welcome.
This is how powershell works, basically everything that you print out will be returned as the function output. So don't output extra stuff. To force something to not output stuff you can do:
$null = some-command_that_outputs_unwanted_things
since everybody is obsessed with Out-Null I'll add this link showing several other ways to do that.
Within a function, everything you don't assign or pipe to a consuming cmdlet will get put to the pipeline and returned from the function - even if you don't explicit return it. In fact the return keyword doesn't do anything in PowerShell so the following is equivalent:
function Test-Func
{
"Hello World"
}
function Test-Func
{
return "Hello World"
}
So it looks like your writeMessageLog puts anything on the pipeline thus you have to either assign the value to anything:
$notUsed = writeMessageLog($msg)
or (prefered) pipe it to the Out-Null cmdlet:
writeMessageLog($msg) | Out-Null
I'm using there functions to serialize and deserialize objects in Powershell 2.0 from question PowerShell 2.0 ConvertFrom-Json and ConvertTo-Json implementation
function ConvertTo-Json20([object] $item){
add-type -assembly system.web.extensions
$ps_js=new-object system.web.script.serialization.javascriptSerializer
return $ps_js.Serialize($item)
}
function ConvertFrom-Json20([object] $item){
add-type -assembly system.web.extensions
$ps_js=new-object system.web.script.serialization.javascriptSerializer
return $ps_js.DeserializeObject($item)
}
But when i run example:
$json = "[{'a':'b'},{'c':'d'}]"
$o = ConvertFrom-Json20 $json
$newJson = ConvertTo-Json20 $o
I've got error:
Exception calling "Serialize" with "1" argument(s): "A circular reference was detected while serializing an object of t
ype 'System.Management.Automation.PSParameterizedProperty'."
At line:4 char:28
+ return $ps_js.Serialize <<<< ($item)
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
How can I resolve this error?
P.S.
I apologize in advance. What was not able to add a comment to the original question ...
There are two problem in code:
PowerShell enumerate collections, so instead of one array ConvertFrom-Json20 return two dictionaries. It is not big deal by itself, but:
return statement in PowerShell v2 wrap returned objects by PSObject. As result $o contain array with two PSObject, and JSON serializer can not work with them properly.
To prevent collection enumeration you can use unary comma operator. This operator create array with single element, and array will be enumerated instead of collection. And since PowerShell return output even without return statement, you can just remove it.
I am having some issue in calling a function using named parameters.
This is the declaration of the function in a separate file (Security.ps1):
function Add-SSRSItemSecurity
(
[Parameter(Position=0,Mandatory=$false)]
[Alias("SSRSrange")]
[string]$range,[Parameter(Position=1,Mandatory=$false)]
[Alias("path")]
[string]$itemPath,
[Parameter(Position=2,Mandatory=$false)]
[Alias("grp")]
[string]$groupUserName,
[Parameter(Position=3,Mandatory=$false)]
[Alias("SSRSrole")]
[string]$role,
[Parameter(Position=2)]
[bool]$inherit=$true
)
I then call this function in another Host.ps1 script as:
Set-Location 'C:\SSRSJobs'
. .\SSRSsecurity.ps1
This call works in the Host file:
Add-SSRSItemSecurity -range "server1" -itemPath "/Test" -groupUserName "CN\Group" -role "Browser"
I tried to pass in multiple parameters to the function as a loop, but calling new variables each time:
$securityArray = #()
$securityArray = Get-Content -Path "C\ReleaseSecurity.txt"
foreach($line in $securityArray)
{
Add-SSRSItemSecurity $line;
}
The file having:
-range "server1" -itemPath "/Test" -groupUserName "CN\Group" -role "Browser"
-range "server2" -itemPath "/Test" -groupUserName "CN\Group" -role "Browser"
-range "server3" -itemPath "/Test" -groupUserName "CN\Group" -role "Browser"
The error I get is:
Add-SSRSItemSecurity : Cannot bind positional parameters because no names were given.
At line:229 char:27
+ Add-SSRSItemSecurity <<<< $line;
+ CategoryInfo : InvalidArgument: (:) [Add-SSRSItemSecurity], ParameterBindingExcepti
on
+ FullyQualifiedErrorId : AmbiguousPositionalParameterNoName,Add-SSRSItemSecurity
Inspecting the string, the $line variable does hold correct naming for parameters. I've tried all sorts of error trapping, but I'm unable to get a decent error message other than the above. I've also tried forms of quoting, but I cannot get the function to see the name binding.
Can multiple variables be called in a function that are bound to just a PowerShell variable name?
You can use splatting for that. Save the parameters as a CSV like this:
"range","itemPath","groupUserName","role"
"server1","/Test","CN\Group","Browser"
"server2","/Test","CN\Group","Browser"
"server3","/Test","CN\Group","Browser"
and load it like this:
Import-Csv 'C:\ReleaseSecurity.txt' | % {
Add-SSRSItemSecurity #_
}
I'm working on a script that will effectively take the time and any input I provide it and spit it out on to an existing text file.
The parameters I created were $Input and $logPath. $Input should be able to take piped input or use "-Input" while the $logPath parameter would require "-logpath".
I'm not sure what I've done wrong since I can't see those switches when using auto complete in the ISE. If I try to pipe input I get:
"Out-LogFile : The input object cannot be bound to any parameters for
the command either because the command does not take pipeline input or
the input and its properties do not match any of the parameters that
take pipeline input. At line:1 char:10"
If I try to stick to the switches instead of piping input I can use one but once I've input one I can't use the second switch.
Here is my function, any help is appreciated:
function Global:Out-LogFile {
#Planned function for outputting to a specified log file.
<#
.SYNOPSIS
Adds to log files.
.DESCRIPTION
Updates a specified log file with a new line. The log file must already exist. See: Get-help New-LogFile
.PARAMETER logPath
Specifies a path for your new log (including file name).
.EXAMPLE
Get-ChildItem C:\ | Out-LogFile
Piped input.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory=$True,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True,
HelpMessage="input to append to logFile",
ParameterSetName='Input')]
[string[]]$Input,
[Parameter(Mandatory=$True,
HelpMessage="Please enter the path to your log file",
ParameterSetName='logPath')]
[string]$logPath
)
#Check to see that $logpath physically exists as a log file.
Try {
Write-Verbose Checking for existing log file.
gi -Path $logPath -ErrorVariable $logPathError | Out-Null
}
Catch {
throw "quitting! logPath does not exist, did you use the -logpath parameter? see: $logPathError"
}
#Create new time object, give it properties of Hours, minutes, seconds, milliseconds.
Try {
Write-Verbose "Creating Time object for recording log timestamps"
$time = New-Object PSObject -ErrorVariable $timeError
$time | Add-Member -MemberType NoteProperty -Name Hours -Value (Get-Date).Hour
$time | Add-Member -MemberType NoteProperty -Name Minutes -Value (Get-Date).Minute
$time | Add-Member -MemberType NoteProperty -Name Seconds -Value (Get-Date).Second
$time | Add-Member -MemberType NoteProperty -Name Milliseconds -Value (Get-Date).Millisecond
#declare "currentTime" variable used to place timestamps in log files.
[string]$currentTime = $time.Hours.ToString() + ":" + $time.Minutes.ToString() + ":" + $time.Seconds.ToString() + ":" + $time.Milliseconds.ToString() + " "
}
Catch {
throw "Can't create PSObject: time. See: $timeError"
}
Try {
#Append data to log file
Write-Verbose "Writing line to log file"
[string]$currentTime + [string]$Input >> $logPath
}
Catch {
throw "Quitting! Can't write to log file"
}
}
You have specified different parameter set names for $Input and $logPath. This effectively means that they are mutually exclusive and PowerShell will not let you use both at the same time (this is why they are not showing up in the ISE's autocomplete). Since you always want to use both at the same time in this case, there's really no need to specify custom parameter sets at all.
Other things to note:
While I have you here let me just point out a few other things that may be issues:
You've named your input parameter $input, which you shouldn't do because $input is a pre-defined/auto-generated variable in PowerShell (specifically, $input is an enumerator object that provides access to the current pipeline) so giving a function parameter the same name could cause your script to behave in unexpected ways.
For example, if you try to pass a value to the parameter manually like this:
Out-LogFile -Input "string1" -logPath test.txt
Your script will output nothing because $input refers to the pipeline which doesn't have anything in it in this case. I suggest renaming $input to $Message or $InputObject, which is consistent with other Out- cmdlets.
This block has a couple of issues:
Try {
Write-Verbose Checking for existing log file.
gi -Path $logPath -ErrorVariable $logPathError | Out-Null
}
Catch {
throw "quitting! logPath does not exist, did you use the -logpath parameter? see: $logPathError"
}
Firstly, it will always throw an exception because your Write-Verbose text is not in quotes and so rather than the entire sentence being treated as a string, the word "Checking" is passed to Write-Verbose's Message parameter and then "for" is passed to the next positional parameter that accepts a string (which, of course, doesn't exist). As a result, you'll receive a 'ParameterBindingException` that says something along the lines of:
Write-Verbose : A positional parameter cannot be found that accepts argument 'for'.
Or rather, you would see that exception, except you have caught it and are then throwing another exception in the catch block that says the script is quitting and that logPath doesn't exist.
But even if you fix the unquoted string on Write-Verbose, the script won't quit and won't throw your custom exception if Get-Item fails to find $logPath. This is because when Get-Item can't find an item, it throws an ItemNotFoundException.
In PowerShell, there are Terminating Errors and Non-Terminating Errors. ItemNotFoundException. is a non-terminating error. Terminating errors always terminate the script (hence the name) but non-terminating errors are dealt with differently depending the current $ErrorActionPreference. By default, the $ErrorActionPreference is set to "Continue" which essentially means that when a non-terminating exception is thrown, the error is shown and the script continues; it isn't caught by a try-catch block.
A better way of determining whether the file exists is via the Test-Path cmdlet:
if (!(Test-Path -Path $logPath))
{
throw [System.IO.FileNotFoundException]("Could not find the file specified in logPath")
}
This entire block is unnecessary (and a very roundabout way of getting a time string):
#Create new time object, give it properties of Hours, minutes, seconds, milliseconds.
Try {
Write-Verbose "Creating Time object for recording log timestamps"
$time = New-Object PSObject -ErrorVariable $timeError
$time | Add-Member -MemberType NoteProperty -Name Hours -Value (Get-Date).Hour
$time | Add-Member -MemberType NoteProperty -Name Minutes -Value (Get-Date).Minute
$time | Add-Member -MemberType NoteProperty -Name Seconds -Value (Get-Date).Second
$time | Add-Member -MemberType NoteProperty -Name Milliseconds -Value (Get-Date).Millisecond
#declare "currentTime" variable used to place timestamps in log files.
[string]$currentTime = $time.Hours.ToString() + ":" + $time.Minutes.ToString() + ":" + $time.Seconds.ToString() + ":" + $time.Milliseconds.ToString() + " "
}
Catch {
throw "Can't create PSObject: time. See: $timeError"
}
The easiest way to get a date/time string in a particular format is to call the ToString() method on a DateTime object. In the parentheses, we can specify a standard or custom format. Get-Date returns a DateTime object so we can just write:
$currentTime = (Get-Date).ToString("hh:mm:ss:fff") + " "
Which basically does the same thing except that it's less work for both you and PowerShell.
Finally, there's not really any reason to enclose your write operation in a try-catch block. If the write operation were to fail (due to the file being read-only, for example) that would be a non-terminating error and wouldn't be caught by your catch anyway.