The PowerShell version I am using is 5.0. I have a situation where I need to use a function and call it within a switch statement. My work involves to do couple of operations.
In brief, my scenario is the inputs for the switch statement are two A and B. I have to call a function declared in switch "condition A" in switch "conditionB", I mean execute the same set of operation.
Exception:
installsw is not recognized as the name of a cmdlet, function, script file,
or operable program. Check the spelling of the name,
CategoryInfo : ObjectNotFound: (regupgrade:String) [], CommandNotFoundException
FullyQualifiedErrorId : CommandNotFoundException
$condi = Read-Host -Prompt "Enter the input"
switch ($condi) {
A {
function installsw() {
Write-Host "install necessary sw"
install some s/w using "Start-Process" Command
}
installsw
}
B {
Write-Host "s/w upgrade"
installsw
$logs = Copy-Item -Path"D:/var" -Destination "D:/Temp"
}
}
Define installsw before the switch. Unless $condi is 'A', the installsw function won't be defined.
Btw, you can run any pipeline inside the first switch line:
switch (Read-Host -Prompt "Enter the input") {
Related
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
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}
First time in PowerShell 5 and I'm having trouble calling a function that writes messages to a file from another function. The following is a simplified version of what I'm doing.
workflow test {
function logMessage {
param([string] $Msg)
Write-Output $Msg
}
function RemoveMachineFromCollection{
param([string]$Collection, [string]$Machine)
# If there's an error
LogMessage "Error Removing Machine"
# If all is good
LogMessage "successfully remove machine"
}
$Collections = DatabaseQuery1
foreach -parallel($coll in $Collections) {
logMessage "operating on $coll collection"
$Machines = DatabaseQuery2
foreach($Mach in $Machines) {
logMessage "Removing $Mach from $coll"
RemoveMachineFromCollection -Collection $coll -Machine $Mach
}
}
}
test
Here's the error it generates:
The term 'logMessage' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (logMessage:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : [localhost]
I've tried moving the logMessage function around in the file and even tried Global scope.
In any other language I would be able to call logMessage from any other function. As that's the purpose of a function.
What's the "Workflow way" of reusing a block of code?
Do I need to create some logging module that gets loaded into the Workflow?
You could move the functions and function call to an InlineScript (PowerShell ScriptBlock) inside the workflow like below.
workflow test {
InlineScript
{
function func1{
Write-Output "Func 1"
logMessage
}
function logMessage{
Write-Output "logMessage"
}
func1
}
}
Would Output:
Func 1
logMessage
As #JeffZeitlin mentioned in his answer, workflows are not PowerShell and are much more restrictive. The InlineScript block allows for normal PowerShell code to be interpreted however the scope will be tied to the InlineScript block. For instance, if you define the functions in the script block then attempt to call the func1 function outside of the InlineScript block (but still within the workflow) it will fail because it is out of scope.
The same would happen if you define the two functions either outside of the workflow or inside of the workflow but not in an InlineScript block.
Now for an example of how you can apply this to running a foreach -parallel loop.
workflow test {
## workflow parameter
param($MyList)
## parallel foreach loop on workflow parameter
foreach -parallel ($Item in $MyList)
{
## inlinescript
inlinescript
{
## function func1 declaration
function func1{
param($MyItem)
Write-Output ('Func 1, MyItem {0}' -f $MyItem)
logMessage $MyItem
}
## function logMessage declaration
function logMessage{
param($MyItem)
Write-Output ('logMessage, MyItem: {0}' -f $MyItem)
}
## func1 call with $Using:Item statement
## $Using: prefix allows us to call items that are in the workflow scope but not in the inlinescript scope.
func1 $Using:Item
}
}
}
Example call to this workflow would look like this
PS> $MyList = 1,2,3
PS> test $MyList
Func 1, MyItem 3
Func 1, MyItem 1
Func 1, MyItem 2
logMessage, MyItem: 3
logMessage, MyItem: 2
logMessage, MyItem: 1
You will notice (and as expected) the output order is random since it was run in parallel.
Powershell requires that functions be defined before use ('lexical scope'). In your example, you are calling the logMessage function before you have defined it.
You have also structured your example as a Powershell workflow. Workflows have some restrictions that ordinary scripts do not; you need to be aware of those differences. I did this search to find some descriptions and discussions of the differences; the first "hit" provides good information. I have not (yet) found anything saying whether functions can be defined in workflows, but I would be very wary of defining functions within functions (or workflows) in the first place.
Your logMessage function is not visible from within func1 function. It's valid even though logMessage function is declared above func1 one.
For this simple case, you could use nested functions as follows:
workflow test {
function func1 {
function logMessage {
Write-Output "logMessage"
}
Write-Output "Func 1"
logMessage
}
func1
}
test
Output:
PS D:\PShell> D:\PShell\SO\41770877.ps1
Func 1
logMessage
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 #_
}
Why is the following code not working? According to this article the usage of global should be correct: http://technet.microsoft.com/en-us/library/ff730957.aspx
Function global:writeLog {
param($logType, $logString, $logFile)
$fileStream = New-Object IO.FileStream $logFile ,'Append','Write','Read'
$streamWriter = New-Object System.IO.StreamWriter $fileStream
$time = get-date -Format "hh:mm:ss"
$streamWriter.writeLine("[${time}][$logType] ${logString}")
$streamWriter.close()
}
$temp = {
writeLog -logType "INFO" -logString "Test" -logFile "d:\scripts\powershell\logtest.txt"
}
Start-Job -ScriptBlock $temp
get-job | receive-job -AutoRemoveJob -Wait
This is the exception that powershell throws
The term 'writeLog' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
+ CategoryInfo : ObjectNotFound: (writeLog:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
+ PSComputerName : localhost
From the documentation of Start-Job:
A Windows PowerShell background job runs a command "in the background" without interacting with the current session.
Therefor, the current session scope is ignored.
Trivial Solution: Define the function inside the scriptblock.
$JobScript = {
function write-log {
....
}
write-log <parameters>
}
Alternatively, check these related questions:
Powershell: passing parameters to a job
Variables in Start-Job
PowerShell jobs actually run in a separate PowerShell process. You can see this like so:
$pid
Start-Job {$pid} | Receive-Job -Wait
Where $pid is the current PowerShell's process id.
Anything that needs to be accessed from the script that runs in the job, must be either defined in the scriptblock passed to Start-Job i.e. function defined in the script block or as parameters passed into the script block using the -ArgumentList parameter on Start-Job or the script can dot source another script (or import a module) that contains the functions it needs. Personally, I would put shared functions in a module like Utils.psm1 and then import like so:
Start-Job {param($scriptdir) Import-Module $scriptdir\Utils.psm1; ...} -Arg $PSScriptRoot
Define the function in a script block, then use
Invoke-Command with NoNewScope to get it in the current scope
The InitializationScript parameter to get it into the job
#Create Shared Functions Script Block
[scriptblock] $func = {function getPID() {write-output "Function Running in process id: $pid!"}}
#Set up functions in normal script scope so they're accessible here
Invoke-Command -NoNewScope -ScriptBlock $func
write-output "Main script started"
#run the function from the parent script
getPID
#Create background job script
$jobScript = {getPID}
#Run background job
write-output "starting background job"
Start-Job $jobScript -name "Job1" -InitializationScript $func
get-job | Receive-Job
get-job | Stop-Job
get-job | Remove-Job