ChatGPT解决这个技术问题 Extra ChatGPT

In PowerShell, how do I define a function in a file and call it from the PowerShell commandline?

I have a .ps1 file in which I want to define custom functions.

Imagine the file is called MyFunctions.ps1, and the content is as follows:

Write-Host "Installing functions"
function A1
{
    Write-Host "A1 is running!"
}
Write-Host "Done"

To run this script and theoretically register the A1 function, I navigate to the folder in which the .ps1 file resides and run the file:

.\MyFunctions.ps1

This outputs:

Installing functions
Done

Yet, when I try to call A1, I simply get the error stating that there is no command/function by that name:

The term 'A1' 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.
At line:1 char:3
+ A1 <<<<
    + CategoryInfo          : ObjectNotFound: (A1:String) [], CommandNotFoundException
    + FullyQualifiedErrorId : CommandNotFoundException

I must misunderstand some PowerShell concepts. Can I not define functions in script files?

Note that I have already set my execution policy to 'RemoteSigned'. And I know to run .ps1 files using a dot in front of the file name: .\myFile.ps1

Nice link on loading functions at PS startup: sandfeld.net/powershell-load-your-functions-at-startup

T
The Red Pea

Try this on the PowerShell command line:

. .\MyFunctions.ps1
A1

The dot operator is used for script include, aka "dot-sourcing" (or "dot source notation")


Well, it means "run this in the current context instead of a child context."
It means source the contents of this file. Same as in bash. ss64.com/bash/period.html
It doesn't seem to work very well though (at least from ISE) unless you run .\MyFunctions.ps1 first to make it available. I'm not sure about running strictly from powershell.exe.
I thought it was counter-intuitive that the dot-sourcing used the path relative to the pwd rather than the script, so I would urge people to look to JoeG's answer instead and use modules.
@Spork . "$PSScriptRoot\MyFunctions.ps1". Availalbe starting in v3, before that see stackoverflow.com/questions/3667238/…. It is VERY common.
D
Dave F

What you are talking about is called dot sourcing. And it's evil. But no worries, there is a better and easier way to do what you are wanting with modules (it sounds way scarier than it is). The major benefit of using modules is that you can unload them from the shell if you need to, and it keeps the variables in the functions from creeping into the shell (once you dot source a function file, try calling one of the variables from a function in the shell, and you'll see what I mean).

So first, rename the .ps1 file that has all your functions in it to MyFunctions.psm1 (you've just created a module!). Now for a module to load properly, you have to do some specific things with the file. First for Import-Module to see the module (you use this cmdlet to load the module into the shell), it has to be in a specific location. The default path to the modules folder is $home\Documents\WindowsPowerShell\Modules.

In that folder, create a folder named MyFunctions, and place the MyFunctions.psm1 file into it (the module file must reside in a folder with exactly the same name as the PSM1 file).

Once that is done, open PowerShell, and run this command:

Get-Module -listavailable

If you see one called MyFunctions, you did it right, and your module is ready to be loaded (this is just to ensure that this is set up right, you only have to do this once).

To use the module, type the following in the shell (or put this line in your $profile, or put this as the first line in a script):

Import-Module MyFunctions

You can now run your functions. The cool thing about this is that once you have 10-15 functions in there, you're going to forget the name of a couple. If you have them in a module, you can run the following command to get a list of all the functions in your module:

Get-Command -module MyFunctions

It's pretty sweet, and the tiny bit of effort that it takes to set up on the front side is WAY worth it.


What about if your functions are pertinent only to that given PowerShell application? I mean, if you install a package of PS1's to do a job somewhere, you might not want every function in your profile, right?
In that case, I'd create a module for that specific application, and either load it before running the scripts (if working interactively), or load it within the script. But generally speaking if you have code that is specific only to a given task, you'd want those functions in the script. Personally I only write functions which generically do one thing. If a piece of code is hyper specialized it doesn't really make sense to wrap it in a function or module (unless there are several scripts that use that same code, then it might make sense).
It's NOT necessary the module file to be in a folder with exactly the same name as the PSM1 file. It can be done like Import-Module .\buildsystem\PSUtils.psm1
@MichaelFreidgeim if it is as simple as just changing the . with Import-Module and renaming the extension, and doesn't require the modules to be placed in a specific folder, i.e. I can have it in any directory I want, just like with dot sourcing, is there any reason to even do dot sourcing over modules, considering the benefits that come for scoping? (unless of course those scope "issues" is what you want)
@Abdul, dot sourcing is simpler, modules are much more powerful. See stackoverflow.com/questions/14882332/…
C
Community

. "$PSScriptRoot\MyFunctions.ps1" MyA1Func

Availalbe starting in v3, before that see How can I get the file system location of a PowerShell script?. It is VERY common.

P.S. I don't subscribe to the 'everything is a module' rule. My scripts are used by other developers out of GIT, so I don't like to put stuff in specific a place or modify system environment variables before my script will run. It's just a script (or two, or three).


FWIW, you don't have to do either of those things in order to run script in a module.
@NickCox I'd love to see some examples of that. Do you have any? +10 if the example is from an OSS project. Specifically, an example of PS module being loaded via a relative path (not PSModulePath or without customizing PSModulePath), and non-trivial example (i.e. where the module has benefits over normal script scoping).
I frequently import the FluentMigrator.PowerShell module from a relative path. That lets us check it into source control and ensure that everyone's using the same version. It works well.
I'm not sure on the relative pros and cons of packaging it as a module vs as a script: perhaps that's one to discuss with the author? I guess the abaility to Get-Command -Module FluentMigrator.PowerShell is quite nice?
@NickCox You didn't fully qualify the module path in that command, which means it wouldn't be found unless you either copy the module into a global module folder or add your GIT folder to a global environment variable. I think you just demonstrated my point.
J
Jonny

You certainly can define functions in script files (I then tend to load them through my Powershell profile on load).

First you need to check to make sure the function is loaded by running:

ls function:\ | where { $_.Name -eq "A1"  }

And check that it appears in the list (should be a list of 1!), then let us know what output you get!


In PowerShell function is treated as a directory so it's the same as saying c:\ or d:\. Equally you it will work without the backslash so ls function: | where { $_.Name -eq "A1" }
s
sɐunıɔןɐqɐp

You can add function to:

c:\Users\David\Documents\WindowsPowerShell\profile.ps1

An the function will be available.


b
bergmeister

If your file has only one main function that you want to call/expose, then you can also just start the file with:

Param($Param1)

You can then call it e.g. as follows:

.\MyFunctions.ps1 -Param1 'value1'

This makes it much more convenient if you want to easily call just that function without having to import the function.


I should also note that I discovered today (after a colleague of mine told me) that PowerShell automatically adds the [CmdletBinding()] attribute and upgrades it for free to an advanced function. :-)
B
Bytekoder

Assuming you have a module file called Dummy-Name.psm1 which has a method called Function-Dumb()

Import-Module "Dummy-Name.psm1";
Get-Command -Module "Function-Dumb";
#
#
Function-Dumb;

关注公众号,不定期副业成功案例分享
Follow WeChat

Success story sharing

Want to stay one step ahead of the latest teleworks?

Subscribe Now