ChatGPT解决这个技术问题 Extra ChatGPT

Call PowerShell script PS1 from another PS1 script inside Powershell ISE

I want call execution for a myScript1.ps1 script inside a second myScript2.ps1 script inside Powershell ISE.

The following code inside MyScript2.ps1, works fine from Powershell Administration, but doesn't work inside PowerShell ISE:

#Call myScript1 from myScript2
invoke-expression -Command .\myScript1.ps1

I obtain the following error when I execute MyScript2.ps1 from PowerShell ISE:

The term '.\myScript1.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.


J
JasonMArcher

In order to find the location of a script, use Split-Path $MyInvocation.MyCommand.Path (make sure you use this in the script context).

The reason you should use that and not anything else can be illustrated with this example script.

## ScriptTest.ps1
Write-Host "InvocationName:" $MyInvocation.InvocationName
Write-Host "Path:" $MyInvocation.MyCommand.Path

Here are some results.

PS C:\Users\JasonAr> .\ScriptTest.ps1
InvocationName: .\ScriptTest.ps1
Path: C:\Users\JasonAr\ScriptTest.ps1

PS C:\Users\JasonAr> . .\ScriptTest.ps1
InvocationName: .
Path: C:\Users\JasonAr\ScriptTest.ps1

PS C:\Users\JasonAr> & ".\ScriptTest.ps1"
InvocationName: &
Path: C:\Users\JasonAr\ScriptTest.ps1

In PowerShell 3.0 and later you can use the automatic variable $PSScriptRoot:

## ScriptTest.ps1
Write-Host "Script:" $PSCommandPath
Write-Host "Path:" $PSScriptRoot
PS C:\Users\jarcher> .\ScriptTest.ps1
Script: C:\Users\jarcher\ScriptTest.ps1
Path: C:\Users\jarcher

A late addition: If you're worried abot variance (or, actually, just want "solid" code) you probably want to use "Write-Output" rather than "Write-Host".
It would be good to see an example use of Split-Path in the answer. You also need to show calling a script inside another script really.
y
ycomp

I am calling myScript1.ps1 from myScript2.ps1 .

Assuming both of the script are at the same location, first get the location of the script by using this command :

$PSScriptRoot

And, then, append the script name you want to call like this :

& "$PSScriptRoot\myScript1.ps1"

This should work.


& "$PSScriptRoot\myScript1.ps1" is enough
& $PSScriptRoot\myScript1.ps1 IS enough (without quotes)
@GregorSimončič Only if the variable doesn't include whitespace characters. If the variable contains whitespace, the unquoted command would probably introduce a syntax error due to "splitting" the command into two pieces. Keeping the quotes is the safest way to go.
S
Shay Levy

The current path of MyScript1.ps1 is not the same as myScript2.ps1. You can get the folder path of MyScript2.ps1 and concatenate it to MyScript1.ps1 and then execute it. Both scripts must be in the same location.

## MyScript2.ps1 ##
$ScriptPath = Split-Path $MyInvocation.InvocationName
& "$ScriptPath\MyScript1.ps1"

How I have to initialize $MyInvocation variable?
You don't, it's an automatic variable.
It works, but obtain the following error before really execution of called script: Split-Path : Cannot bind argument to parameter 'Path' because it is an empty string. At line:4 char:25 + $ScriptPath = Split-Path <<<< $MyInvocation.InvocationName + CategoryInfo : InvalidData: (:) [Split-Path], ParameterBindingValidationException + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,Microsoft.PowerShell.Commands.SplitPathCommand
create a new script put: $MyInvocation.InvocationName in it and run the script. Do you get the path of the script?
@JasonMArcher - Why instead? As far as I know both give the same output?
n
noelicus

One line solution:

& ((Split-Path $MyInvocation.InvocationName) + "\MyScript1.ps1")

This is nice, but why not just & '.\MyScript1.ps' if the script resides in the same directory?
That's using current directory not script directory. Sure they're often the same ... but not always!
I question the same line - if you run a "Set-Location" command to move to that folder before you call it, that should achieve both by setting the location and making it current at the same time.
Firstly, that's not one-line any more :). But also, it's only applicable if you don't mind changing the working directory, which is quite the assumption and not good practice.
c
cpoDesign

This is just additional info to answers in order to pass argument into the another file

Where you expect argument

PrintName.ps1

Param(
    [Parameter( Mandatory = $true)]
    $printName = "Joe"    
)


Write-Host $printName

How to call the file

Param(
    [Parameter( Mandatory = $false)]
    $name = "Joe"    
)


& ((Split-Path $MyInvocation.InvocationName) + "\PrintName.ps1") -printName $name

If you do not do not provide any input it will default to "Joe" and this will be passed as argument into printName argument in PrintName.ps1 file which will in turn print out the "Joe" string


R
Rrr

To execute easily a script file in the same folder (or subfolder of) as the caller you can use this:

# Get full path to the script:
$ScriptRoute = [System.IO.Path]::GetFullPath([System.IO.Path]::Combine($PSScriptRoot, "Scriptname.ps1"))

# Execute script at location:
&"$ScriptRoute"

E
Emil Bogopolskiy

You may have found the answer for it already, but here is what I do.

I usually place this line at the beginning of my installation scripts:

if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } #In case if $PSScriptRoot is empty (version of powershell V.2).  

Then I can use $PSScriptRoot variable as a location of the current script(path), like in the example bellow:

if(!$PSScriptRoot){ $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } #In case if $PSScriptRoot is empty (version of powershell V.2).  

Try {
If (Test-Path 'C:\Program Files (x86)') {
    $ChromeInstallArgs= "/i", "$PSScriptRoot\googlechromestandaloneenterprise64_v.57.0.2987.110.msi", "/q", "/norestart", "/L*v `"C:\Windows\Logs\Google_Chrome_57.0.2987.110_Install_x64.log`""
    Start-Process -FilePath msiexec -ArgumentList $ChromeInstallArgs -Wait -ErrorAction Stop
    $Result= [System.Environment]::ExitCode
} Else {
    $ChromeInstallArgs= "/i", "$PSScriptRoot\googlechromestandaloneenterprise_v.57.0.2987.110.msi", "/q", "/norestart", "/L*v `"C:\Windows\Logs\Google_Chrome_57.0.2987.110_Install_x86.log`""
    Start-Process -FilePath msiexec -ArgumentList $ChromeInstallArgs -Wait -ErrorAction Stop
    $Result= [System.Environment]::ExitCode
    }

} ### End Try block


Catch  {
    $Result = [System.Environment]::Exitcode
    [System.Environment]::Exit($Result)
   }
[System.Environment]::Exit($Result)

In your case, you can replace

Start-process... line with

Invoke-Expression $PSScriptRoot\ScriptName.ps1

You can read more about $MYINVOCATION and $PSScriptRoot automatic variables on the Microsoft site: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables


N
Nico Osorio

I had a similar problem and solved it this way.

My working directory is a general script folder and serveral particular script folder in same root, i need to call particular script folder (which call general script with the parameter of the particular problem). So working directory is like this

\Nico\Scripts\Script1.ps1
             \Script2.ps1
      \Problem1\Solution1.ps1
               \ParameterForSolution1.config
      \Problem2\Solution2.ps1
               \ParameterForSolution2.config

Solutions1 and Solutions2 call the PS1 in Scripts folder loading the parameter stored in ParameterForSolution. So in powershell ISE i run this command

.\Nico\Problem1\Solution1.PS1

And the code inside Solution1.PS1 is:

# This is the path where my script is running
$path = split-path -parent $MyInvocation.MyCommand.Definition

# Change to root dir
cd "$path\..\.."

$script = ".\Script\Script1.PS1"

$parametro = "Problem1\ParameterForSolution1.config"
# Another set of parameter Script1.PS1 can receive for debuggin porpuose
$parametro +=' -verbose'

Invoke-Expression "$script $parametro"

Z
Zack A

I submit my example for consideration. This is how I call some code from a controller script in the tools I make. The scripts that do the work also need to accept parameters as well, so this example shows how to pass them. It does assume the script being called is in the same directory as the controller script (script making the call).

[CmdletBinding()]
param (
[Parameter(Mandatory = $true)]
[string[]]
$Computername,

[Parameter(Mandatory = $true)]
[DateTime]
$StartTime,

[Parameter(Mandatory = $true)]
[DateTime]
$EndTime
)

$ZAEventLogDataSplat = @{
    "Computername" = $Computername
    "StartTime"    = $StartTime
    "EndTime"      = $EndTime
}

& "$PSScriptRoot\Get-ZAEventLogData.ps1" @ZAEventLogDataSplat

The above is a controller script that accepts 3 parameters. These are defined in the param block. The controller script then calls the script named Get-ZAEventLogData.ps1. For the sake of example, this script also accepts the same 3 parameters. When the controller script calls to the script that does the work, it needs to call it and pass the parameters. The above shows how I do it by splatting.


P
Pop

I had a problem with this. I didn't use any clever $MyInvocation stuff to fix it though. If you open the ISE by right clicking a script file and selecting edit then open the second script from within the ISE you can invoke one from the other by just using the normal .\script.ps1 syntax. My guess is that the ISE has the notion of a current folder and opening it like this sets the current folder to the folder containing the scripts. When I invoke one script from another in normal use I just use .\script.ps1, IMO it's wrong to modify the script just to make it work in the ISE properly...


Downvoted this because the issue is the failure of the script to work when not run from the folder containing it. It's not about modifying the script to work in the ISE specifically, it's about modifying the script to be portable and flexible enough to handle being run from a different directory. The behavior of the code in this case shouldn't depend on where you've invoked it from.
T
Tyler Curtis Jowers

How do you run PowerShell built-in scripts inside of your scripts?

How do you use built-in scripts like

Get-Location
pwd
ls
dir
split-path
::etc...

Those are ran by your computer, automatically checking the path of the script.

Similarly, I can run my custom scripts by just putting the name of the script in the script-block

::sid.ps1 is a PS script I made to find the SID of any user
::it takes one argument, that argument would be the username
echo $(sid.ps1 jowers)


(returns something like)> S-X-X-XXXXXXXX-XXXXXXXXXX-XXX-XXXX


$(sid.ps1 jowers).Replace("S","X")

(returns same as above but with X instead of S)

Go on to the powershell command line and type

> $profile

This will return the path to a file that our PowerShell command line will execute every time you open the app.

It will look like this

C:\Users\jowers\OneDrive\Documents\WindowsPowerShell\Microsoft.PowerShellISE_profile.ps1

Go to Documents and see if you already have a WindowsPowerShell directory. I didn't, so

> cd \Users\jowers\Documents
> mkdir WindowsPowerShell
> cd WindowsPowerShell
> type file > Microsoft.PowerShellISE_profile.ps1

We've now created the script that will launch every time we open the PowerShell App.

The reason we did that was so that we could add our own folder that holds all of our custom scripts. Let's create that folder and I'll name it "Bin" after the directories that Mac/Linux hold its scripts in.

> mkdir \Users\jowers\Bin

Now we want that directory to be added to our $env:path variable every time we open the app so go back to the WindowsPowerShell Directory and

> start Microsoft.PowerShellISE_profile.ps1

Then add this

$env:path += ";\Users\jowers\Bin"

Now the shell will automatically find your commands, as long as you save your scripts in that "Bin" directory.

Relaunch the powershell and it should be one of the first scripts that execute.

Run this on the command line after reloading to see your new directory in your path variable:

> $env:Path

Now we can call our scripts from the command line or from within another script as simply as this:

$(customScript.ps1 arg1 arg2 ...)

As you see we must call them with the .ps1 extension until we make aliases for them. If we want to get fancy.


Wow, thanks for this, there is a lot here. But there are 9 other answers already here. How does this differ? What additional information is it providing?
Doing this allows us to use our custom-scripts inside of other scripts—in the same way that built-in scripts are used inside of our scripts. Do this and as long as you save your scripts in the directory that you put in your path, the computer will automatically find the path of the custom-script when you use it on the command line or in another script
V
Vega

You can also use the following commands:

$Patch = Join-Path -Path $PSScriptRoot -ChildPath "\patch.ps1"<br>
Invoke-Expression "& `"$Patch`""

R
Richard H

If the vars above are null, you are probably using PowerShell ISE. In this case try:

if ($psISE) { Split-Path -Path $psISE.CurrentFile.FullPath } else { $global:PSScriptRoot }

Check out this discussion.