none
Powershell - calling ps1 functions RRS feed

  • Question

  • v3+ context

    Hi

    While there is lots of posts on this topic, many of them are not correct, i.e. I tried some of them, no joy so please bear with me.

    I am having trouble getting the syntax right for invokes. I can successfully do  powershell -Command "& {Set-ExecutionPolicy RemoteSigned -force;. C:\WinUtils\Docs\machine_setup_functions.ps1;setup-oo-report}" from a BAT file.

    It loads the file and runs the function. I have a powershell script engine that allows entries in an XML file to be called. This is all driven from powershell. Samples below .

    I want to be able to call the function in a separate ps1 file from Powershell. (so env vars changes stay in the current context)  I want to be able to call the function in a separate ps1 file and pass some arguments

    I have tried a number of approaches and still get either an error or no error and no action.
    Need to :
    - call a function in a ps1, no arguments
    - call a function in a ps1 with multiple arguments and spaces can be in an argument

    >> Can someone advise what I am doing wrong as I am not sure of the nuiances between invoke-expression and command   ?

    >> I assume if all args have "" around them I should cover most space and other special characters

    e.g. ". F:\ScriptWork\Test_called.ps1;Function_Test_args -Info1 "hello there" -Info2 "#2"" or
    . F:\ScriptWork\Test_called.ps1;Function_Test_args -Info1 "hello there" -Info2 "#2" returns "

    & powershell $theargs
    "powershell.exe : Function_Test_Args : Missing an argument for parameter 'Info2'. "
    (p.s. not that i want to call a separate exe but was trying to see if the web example worked)

    Thanks in advance

    #
    # Sample engine code 
    #
    #case 1
    $function = "Function_Test"
    $script = "C:\temp\test_called.ps1"
    $optionargs = ""
    <#case 2
    $function = "Function_Test_Args"
    $script = "C:\temp\test_called.ps1"
    $optionargs = '"info for 1" "info for 2"' # not really sure the correct way to do this
    #>
        $HasFunc = $function.Length -gt 0
        $theArgs = "{"
        if ($HasFunc)
        {
            $theArgs += ". " + $script + ";" + $function 
        }
        else
        {
            # no function so expect code in ps1 NOT in a function
            $theArgs += ". " + $script 
        }
        if ($optionalArgs.Length > 0)
        {
            $theArgs += " " + $optionalArgs
        }
        $theArgs += "}"
        Invoke-Command -ScriptBlock $theArgs
        # No joy whether i use Invoke-expression, -command, none etc
    #
    # sample called from other ps1
    #
    Function Function_Test
    {
        Write-Output "Function_Test is running"
        Write-Host "Function_Test is running"
    }
    
    Function Function_Test_Args
    {
    param( 
          [Parameter(Mandatory = $True)] 
          [AllowEmptyString()] 
          [String]$Info1,
          [string]$Info2
        )
    
        Write-Output "Function_Test_Args #1 $Info1 #2 $Info2"
        Write-Host "Function_Test_Args #1 $Info1 #2 $Info2"
    }

    Monday, November 2, 2015 12:08 AM

Answers

  • Need to :
    - call a function in a ps1, no arguments

    To call a function just type the name of the function after loading the ps1 by dot sourcing as explained earlier.

    - call a function in a ps1 with multiple arguments and spaces can be in an argument

    To call and function just state the function name

    Myfunction ‘myarg’ ‘myarg’ $a $b

    >> Can someone advise what I am doing wrong as I am not sure of the nuiances between invoke-expression and command   ?

    You do not need to use either of these to call functions.  Invoke-Command is used for remoting.  Invoke-Expression is used to evaluate strings.

    $cmd='dir c:\'
    Invoke-Expression $cmd


    \_(ツ)_/

    • Marked as answer by Greg B Roberts Monday, November 2, 2015 10:34 PM
    Monday, November 2, 2015 7:28 AM
  • Hi

    I don't agree with your assessment this makes no sense and I find you tone aggressive, but you are the expert so I have no choice.

    If we look at the original post I provided a code samples and asked :

     

    It makes no sense because you have not given it a context. Why do you need to do this? Why are you placing all bits of PowerShell in strings? What is the intended purpose?

    I am not being aggressive I am trying to figure out what you are trying to do.  You know what that is but it is not at all clear to us.

    What are you trying to say about functions.  In your mind what is it that calling a function out-of-process" refers to?  All functions run in the current process except when  you use a job, a remote call, a workflow and, at times a DSC (Config Manager Process).  I suspect that you do not mean that when you use the term.  Out-of-process is usually applied to automation calls which are either "in-process" or "out-of-process" .  Using the Scripting.FileSystemObject is an "in-process" call.  Using Excel.Application is an "out-of-process" activation.

    Functions are neither.  They either execute in the current session (runspace) or in another runspace or process such as with Start-Job.


    \_(ツ)_/


    • Edited by jrv Monday, November 2, 2015 7:38 AM
    • Marked as answer by Greg B Roberts Monday, November 2, 2015 10:34 PM
    Monday, November 2, 2015 7:38 AM

All replies

  • Monday, November 2, 2015 1:15 AM
  • Hi

    My sample had this

     $theArgs += ". " + $script + ";" + $function

    In my case I need to include this in a script block or the one line as this is not an interactive powershell sequence

    I could get $theargs =". F:\ScriptWork\helloworld.ps1;Function_Test         - worked "

    to work with a Invoke-Expression $theArgs

    but not pass arguments, tried no -arg format and no -arg, e.g.

    . F:\ScriptWork\helloworld.ps1;Function_Test_args  "hello there"  "#2"


    Thanks


    Monday, November 2, 2015 1:19 AM
  • The correct use of Invoke is this:

    Invoke-Command -ScriptBlock {Param($myarg1,$myarg2);Write-host $myarg1,$myarg2} -argumentlist 'hello','world'

    Try it to see.

    We can also state more clearly"

    $sb={
         Param(
              $myarg1,
              $myarg2
         )
         Write-host $myarg1,$myarg2
    }
    Invoke-Command -ScriptBlock  $sb -argumentlist 'hello','world'
    


    \_(ツ)_/


    • Edited by jrv Monday, November 2, 2015 1:24 AM
    Monday, November 2, 2015 1:21 AM
  • Hi

    My sample had this

     $theArgs += ". " + $script + ";" + $function

    In my case I need to include this in a script block or the one line as this is not an interactive powershell sequence. e.g. line must work from within ps1 function. I also tried with no {}. I will revisit using no invoke-xxx and post back

    Thanks



    That is NOT dot sourcing and what you are doing is unnecessary.  You need to spend some time trying to learn how the "PowerShell" engine works.


    \_(ツ)_/

    Monday, November 2, 2015 1:29 AM
  • Invoke-Command -ScriptBlock {Param($myarg1,$myarg2);Write-host
    $myarg1,$myarg2} -argumentlist 'hello','world'
    as a typed in command

    Invoke-Command

    -ScriptBlock{ $theArgs}

    where $theargs = '. F:\ScriptWork\helloworld.ps1;Function_Test_args "hello there" "Number 2"' does not work.

    thanks

    Monday, November 2, 2015 1:47 AM
  • If you want to use the parameters and call them using invoke-command the script needs to be present in the remote machine path.

    PS C:\> Invoke-Command -ComputerName server1,server2 {C:\myfunction.ps1;myfunction} | Select *
    The term '.\myfunction.ps1' 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.

    If you want to use the local script on remote server then, your script should accept parameters and show results, not the functions.

    Invoke-Command -ComputerName Server1,Server2 -FilePath "C:\Scripts\myfunction.ps1" -Argumentlist "-myarg 1"
    Above works.

    How can my local PowerShell script call a remote PowerShell script?

    You can't use both script block and -filepath together.

    Now for the dot sourcing part, you can't load the function and run it at the same time using invoke-command. To achieve this you need to use session and do it in parts.

    This is a sample:

    Same Script file:

    #Main Script Parameter, can be called directly in single icm
    Param(
    $check
         )
    
    
    #Auto dot sourced when called using -filepath
    	function myfunction
    	{
    
    	#Arguments and all can be used in next session icm
    		Param( 
    		 $FuncArg
    		)
    
    		$env:COMPUTERNAME
    		
    		#Dot sourced function arg call
    		if($FuncArg)
    		{"Function Argument called"}
    
    	}
    
    #Main script parameter called
    if($check)
    {
    	myfunction
    }

    #Cmdlets to run
    $remoteSession = New-PSSession -ComputerName "Server1"
    Invoke-Command -Session $remoteSession -FilePath "C:\Scripts\myfunction.ps1"
    Invoke-Command -Session $remoteSession -ScriptBlock {myfunction}
    Invoke-Command -Session $remoteSession -ScriptBlock {myfunction -FuncArg 1}
    
    
    #Test run
    
    PS > $remoteSession = New-PSSession -ComputerName "Server2"
    PS > Invoke-Command -Session $remoteSession -FilePath "C:\Scripts\myfunction.ps1"
    PS > Invoke-Command -Session $remoteSession -ScriptBlock {myfunction}
    Server2
    PS > Invoke-Command -Session $remoteSession -ScriptBlock {myfunction -FuncArg 1}
    Server2
    Function Argument called
    PS >
    
    Here I have successfully showed you how to load\run a local script with functions on remote machine, without using DOT sourcing it manually.

    References:

    Powershell: How to use Invoke-Command and dot sourcing at the same time?

    Invoke-Command

    -FilePath<String>

    Runs the specified local script on one or more remote computers. Enter the path and file name of the script, or pipe a script path to Invoke-Command. The script must reside on the local computer or in a directory that the local computer can access. Use the ArgumentList parameter to specify the values of parameters in the script.

    When you use this parameter, Windows PowerShell converts the contents of the specified script file to a script block, transmits the script block to the remote computer, and runs it on the remote computer.


    Regards,

    Satyajit

    Please “Vote As Helpful” if you find my contribution useful or “Mark As Answer” if it does answer your question. That will encourage me - and others - to take time out to help you.

    Monday, November 2, 2015 6:36 AM
  • Thanks for the detailed response.

    I do not need any remoting so am happy with an invoke-expression answer. As I indicated in the post, I was not sure of how to get either working .

    I assume that invoke-expression and invoke-command (no remote session) are the same environment variable wise? i.e. changes by the function run, will affect the current scope of environment variables and global variables.
    I understand anything that invokes a separate process will loose any changes to env. variables on return.

    My code sample is for local actions.

    #case 1
    $function
    = "Function_Test"
    $script
    = "C:\temp\test_called.ps1"
    $optionargs
    = ""
    <#case 2
    $function
    = "Function_Test_Args"
    $script
    = "C:\temp\test_called.ps1"
    $optionargs
    = '"info for 1" "info for 2"' # not really sure the correct way to do this
    #>


    Monday, November 2, 2015 6:58 AM
  • What you are posting makes absolutely no sense.

    Try stating what you are trying to do in plain language without technical references.  What is it that you are trying to do?  What is the desired outcome?

    Note that as above.

    Invoke-Command -ScriptBlock {$args} -ArgumentList 'arg1','arg2','arg3'

    You cannot use the variable "function".  It is protected and reserved.

    Type this to se what I mean: "$function:help"  - without the quotes.


    '\_(ツ)_/



    • Edited by jrv Monday, November 2, 2015 7:08 AM
    Monday, November 2, 2015 7:01 AM
  • Hi

    I don't agree with your assessment this makes no sense and I find you tone aggressive, but you are the expert so I have no choice.

    If we look at the original post I provided a code samples and asked :

     "Need to :
    - call a function in a ps1, no arguments
    - call a function in a ps1 with multiple arguments and spaces can be in an argument

    >> Can someone advise what I am doing wrong as I am not sure of the nuiances between invoke-expression and command   ?

    I prefer to call the function "inline" (not out of process)
    The answer has to be able to be called from code, not manually (as I tried to do in the example)

    Thanks

    Monday, November 2, 2015 7:18 AM
  • Hi Greg,

    This should work,just fine on local as well. I have tested your code. Check and let me know, if you are able to reproduce what I have tested.

    Sorry to say but your '#Sample engine code' has some errors

    $optionalArgs.Length > 0 ; you should be using -gt not >
    The variable names are not matching $optionargs = ""

    Rather than complicating the scriptblock formation you should manually test on initial ones.

    Like instead of the #SampleEngineCode, you should simply test using these four lines,

    $theArgs =  {C:\temp\test_called.ps1;Function_Test}
    $theArgs2 = {C:\temp\test_called.ps1;Function_Test_Args "info for 1" "info for 2"}
    
    Invoke-Command -ScriptBlock $theArgs
    Invoke-Command -ScriptBlock $theArgs2
    

    Now as in my earlier post, you can't dot source scripts like this and above cmdlets will fail.


    So it should be called like this.

    $theArgs =  {Function_Test}
    $theArgs2 = {Function_Test_Args "info for 1" "info for 2"}
    
    
    $remoteSession = New-PSSession -ComputerName "Server2"
    Invoke-Command -Session $remoteSession -FilePath "C:\Scripts\test_called.ps1"
    Invoke-Command -Session $remoteSession -ScriptBlock $theArgs
    Invoke-Command -Session $remoteSession -ScriptBlock $theArgs2
    

     


    #Test Run result:

    #This is where your # sample called from other ps1, unedited
    PS C:\Scripts> notepad test_called.ps1
    PS C:\Scripts>
    
    #These lines replaces your # Sample engine code, no mixing of script filenames.
    PS C:\Scripts> $theArgs =  {Function_Test}
    PS C:\Scripts> $theArgs2 = {Function_Test_Args "info for 1" "info for 2"}
    PS C:\Scripts>
    PS C:\Scripts>
    #This is the correct method of calling the script
    PS C:\Scripts> $remoteSession = New-PSSession -ComputerName "Server2"
    PS C:\Scripts> Invoke-Command -Session $remoteSession -FilePath "C:\Scripts\test_called.ps1"
    PS C:\Scripts> Invoke-Command -Session $remoteSession -ScriptBlock $theArgs
    Function_Test is running
    Function_Test is running
    PS C:\Scripts> Invoke-Command -Session $remoteSession -ScriptBlock $theArgs2
    Function_Test_Args #1 info for 1 #2 info for 2
    Function_Test_Args #1 info for 1 #2 info for 2
    PS C:\Scripts>
    


    Regards,

    Satyajit

    Please “Vote As Helpful” if you find my contribution useful or “Mark As Answer” if it does answer your question. That will encourage me - and others - to take time out to help you.

    Monday, November 2, 2015 7:22 AM
  • Need to :
    - call a function in a ps1, no arguments

    To call a function just type the name of the function after loading the ps1 by dot sourcing as explained earlier.

    - call a function in a ps1 with multiple arguments and spaces can be in an argument

    To call and function just state the function name

    Myfunction ‘myarg’ ‘myarg’ $a $b

    >> Can someone advise what I am doing wrong as I am not sure of the nuiances between invoke-expression and command   ?

    You do not need to use either of these to call functions.  Invoke-Command is used for remoting.  Invoke-Expression is used to evaluate strings.

    $cmd='dir c:\'
    Invoke-Expression $cmd


    \_(ツ)_/

    • Marked as answer by Greg B Roberts Monday, November 2, 2015 10:34 PM
    Monday, November 2, 2015 7:28 AM
  • Hi Greg,

    Now for the local part, as you said you don't want remoting (keep jrv's comment in mind upon using Invoke-* cmdlets or directly calling the functions.):

    #Run the script once
    . .\Test_Called.ps1

    #Then these
    $theArgs =  "Function_Test"
    $theArgs2 = "Function_Test_Args 'info for 1' 'info for 2'"

    #Now you can call the functions using iex
    Invoke-Expression $theArgs
    Invoke-Expression $theArgs2


    #But you want it inline
    #Use dot sourcing on any script where you are using these function add this to the start.
    . C:\Scripts\Test_Called.ps1

    #Now that script or Powershell session can call the functions as earlier.


    But you want to call the function anytime anywhere without dotsourcing it. Like you call Get-Service or Get-Process.
    This can be done. You need to read how to make custom modules that auto-load upon thier usage.
    PSv3 onwards this is very useful.

    Modules can be shared to other users or servers as well.


    Regards,

    Satyajit

    Please“Vote As Helpful” if you find my contribution useful or “MarkAs Answer” if it does answer your question. That will encourage me - and others - to take time out to help you.



    • Edited by Satyajit321 Monday, November 2, 2015 7:56 AM
    Monday, November 2, 2015 7:36 AM
  • Hi

    I don't agree with your assessment this makes no sense and I find you tone aggressive, but you are the expert so I have no choice.

    If we look at the original post I provided a code samples and asked :

     

    It makes no sense because you have not given it a context. Why do you need to do this? Why are you placing all bits of PowerShell in strings? What is the intended purpose?

    I am not being aggressive I am trying to figure out what you are trying to do.  You know what that is but it is not at all clear to us.

    What are you trying to say about functions.  In your mind what is it that calling a function out-of-process" refers to?  All functions run in the current process except when  you use a job, a remote call, a workflow and, at times a DSC (Config Manager Process).  I suspect that you do not mean that when you use the term.  Out-of-process is usually applied to automation calls which are either "in-process" or "out-of-process" .  Using the Scripting.FileSystemObject is an "in-process" call.  Using Excel.Application is an "out-of-process" activation.

    Functions are neither.  They either execute in the current session (runspace) or in another runspace or process such as with Start-Job.


    \_(ツ)_/


    • Edited by jrv Monday, November 2, 2015 7:38 AM
    • Marked as answer by Greg B Roberts Monday, November 2, 2015 10:34 PM
    Monday, November 2, 2015 7:38 AM
  • Hi Greg,

    Now for the local part, as you said you don't want remoting (keep jrv's comment in mind upon using Invoke-* cmdlets or directly calling the functions.):

    #Run the script once
    .\Test_Called.ps1

    #Then these
    $theArgs =  "Function_Test"
    $theArgs2 = "Function_Test_Args 'info for 1' 'info for 2'"

    #Now you can call the functions using iex
    Invoke-Expression $theArgs
    Invoke-Expression $theArgs

    That won't work.  You have to dot source the file. You are not dot sourcing it.


    \_(ツ)_/

    Monday, November 2, 2015 7:41 AM

  • But you want to call the function anytime anywhere without dotsourcing it. Like you call Get-Service or Get-Process.
    This can be done. You need to read how to make custom modules that auto-load upon thier usage.
    PSv3 onwards this is very useful.

    Modules can be shared to other users or servers as well.



    Do you suppose that is what is being referred to as "inline".

    "inline" refers to a script in a workflow so it confused me.

    A module has to be loaded the same as a script.  In PS V3 and later the modules will autoload but they still have to be loaded.  A script file of functions only needs to be dot sourced once.


    \_(ツ)_/


    • Edited by jrv Monday, November 2, 2015 7:46 AM
    Monday, November 2, 2015 7:45 AM
  • Hi Greg,

    Now for the local part, as you said you don't want remoting (keep jrv's comment in mind upon using Invoke-* cmdlets or directly calling the functions.):

    #Run the script once
    .\Test_Called.ps1

    #Then these
    $theArgs =  "Function_Test"
    $theArgs2 = "Function_Test_Args 'info for 1' 'info for 2'"

    #Now you can call the functions using iex
    Invoke-Expression $theArgs
    Invoke-Expression $theArgs

    That won't work.  You have to dot source the file. You are not dot sourcing it.


    \_(ツ)_/

    You're right. We need to call the script as . .\Test_called.ps1

    I missed that; was testing using ISE with the file open.


    Regards,

    Satyajit

    Please “Vote As Helpful” if you find my contribution useful or “Mark As Answer” if it does answer your question. That will encourage me - and others - to take time out to help you.

    Monday, November 2, 2015 7:53 AM
  • So what we are trying to do is understand what "dot sourcing" is and why.

    Placing a dot in front of a slash is NOT dot sourcing but many think it is.  I am glad to see that you don't.

    The dot at the beginning type as a command is a way of saying "execute in the current/calling context (scope)  and not in the called context.  That forces all variables and functions declared at "block 0" in the file to work as if the file were typed in or copied and pasted into the current context.  Loading a module does the same thing however a module can be fine tuned as to what is exposed.  A module has a permanent context and is in its own scope except for what it published.

    Invoke anything is not needed here at all.


    \_(ツ)_/

    Monday, November 2, 2015 8:00 AM
  • Thanks Satyajit  - I am in GMT+11 hence the time gap

    Invoke-Expression "$theArgs" works! it does not work if
    Invoke-expression $theArgs   is used

    Because this is a "table driven" (well XML) system, I can't manually add
    ". .\Test_Called.ps1" to the code

    Now to reply to JRV


    Monday, November 2, 2015 10:01 PM
  • Hi - I am in GMT+11 hence the gap

    Thanks for the useful info it is helpful

    Obviously I tried in my mind to hit the balance between brevity and too much detail which failed . Too little and the problem is not solved for the context, too much and it makes it hard to read and more likely a tangent (plus  post comments, to express in shorthand)

    This was part of a build scripting engine I have developed. Which allows powershell, CMD , msbuild and mstest to run. The existing system has a combination of all of these. I successfully call the CMD and others via a process call. I was having trouble getting the powershell to work in the "local call" context. (I,e no Process call to powershell

    (i.e. used for the others

    $Process

    =New-ObjectSystem.Diagnostics.Process)

    As a secondary issue was the control of the environment variables. Generally in our case you want Env vars to live between "task" calls. In the Powershell case, running scripts in in process allows global vars to be used as well.

    In the CMD case I found add

    & (echo Name, Value & SET) > ""$global:SBS_Last_Task_Envs"" at the end of the task call works well to record the ENV state. At the beginning of each task the previous ENV state is restored.

    The equivalent powershell if out of process was (tacked onto the end of the call, e.g "myFunction;SBS_DumpEnvs"

    Function SBS_DumpEnvs
    {
    	Write-Output "Name, Value" | Out-File -Encoding ASCII $global:SBS_Last_Task_Envs
    	Get-Childitem -Path Env:* | Sort-Object Name | foreach{("{0},{1}" -f $_.Name,$_.Value)} | Out-File -Encoding ASCII -Append $global:SBS_Last_Task_Envs
    }
    ...XML master file
    	<Task Name="taske" Description="do stuff taske" Program="powershell" InitScript="" Script="F:\ScriptWork\helloworld.ps1" Function="Function_Test_args" Arguments="'hello there' 'Number 2'" OutputFilter="" />
    	<Task Name="taskf" Description="do stuff taskf" Program="CMD" InitScript="" Script="F:\ScriptWork\helloworld.bat" Function="" Arguments='hello fred "my friend"' OutputFilter="SBS_ProcessLogger" />
    
    Thanks for your time on this

    Monday, November 2, 2015 10:32 PM
  • If you are trying to put strings into a file and load and execute them then this is how we would do that:

    $code=@'
         function test{
               Param($a)
              Write-Host "called function with $a" -fore green
        }
    
        test calling
    
    '@
    
    $sb=[scriptblock]::Create($code)
    $sb.Invoke()
    

    We can also call invoke with arguments.

    If you type $code.GetType() it will be "String".  You can load it from a text file or from XML.  I have stored many functions and data blobs in XML and loaded them using a script bloke and invoke.

    Now ask how to inject global functions into the session.


    \_(ツ)_/

    Monday, November 2, 2015 10:39 PM
  • Well you are doing everything the hardest possible way.  You seem to enjoy the battle so I will go away and let you sort it out.  I suggest learning PowerShell a little better.  Just gluing odd bits together may ultimtelly work but you could do it all in about 10 minutes with a bit more training in PS>

    Good luck.


    \_(ツ)_/

    Monday, November 2, 2015 11:24 PM