318

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

phuclv
  • 37,963
  • 15
  • 156
  • 475
willem
  • 25,977
  • 22
  • 75
  • 115
  • Nice link on loading functions at PS startup: https://sandfeld.net/powershell-load-your-functions-at-startup – Andrew Mar 16 '18 at 14:25

8 Answers8

321

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")

Nate Anderson
  • 18,334
  • 18
  • 100
  • 135
rsc
  • 4,234
  • 2
  • 30
  • 28
  • I did not notice that extra little dot that I had to type. Thanks! What does the first "." mean? Does it mean "install"? – willem May 16 '11 at 11:37
  • 13
    Well, it means "run this in the current context instead of a child context." – JasonMArcher May 17 '11 at 16:47
  • 21
    It means **source** the contents of this file. Same as in **bash**. http://ss64.com/bash/period.html – inquam Mar 02 '12 at 14:59
  • 2
    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. – Mike Cheel Jul 11 '13 at 15:28
  • 1
    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 Oct 03 '14 at 13:08
  • 9
    @Spork `. "$PSScriptRoot\MyFunctions.ps1"`. Availalbe starting in v3, before that see http://stackoverflow.com/questions/3667238/how-can-i-get-the-file-system-location-of-a-powershell-script. It is VERY common. – yzorg Feb 05 '15 at 15:25
  • If you use `cd` to the directory containing the script, type the first few letters of the script and use `tab` to autofill the command line. It will try and guess to dot-source the script file by itself as if it were a cmdlet. – bgmCoder Feb 13 '17 at 23:42
  • Is it possible to do this on one line yet? example: . .\MyFunctions.ps1 A1 – KD_Raj Nov 20 '22 at 15:18
292

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.

Dave F
  • 1,837
  • 15
  • 20
JoeG
  • 4,117
  • 1
  • 16
  • 15
  • 7
    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? – Ian Patrick Hughes Nov 07 '11 at 18:03
  • 4
    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). – JoeG Nov 28 '11 at 17:27
  • PS 3 and later, put the module in the correct location in the path and it will load the module when you enter a command from it. For example, try typing in `get-aduser` on a command with AD module installed but not loaded. It will then load the module and autocomplete into `Get-ADUser'. Correct location in the path depends. Anywhere in $PSModulePath will work. – Jeter-work May 13 '15 at 20:09
  • 39
    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` – Michael Freidgeim Mar 23 '16 at 05:46
  • 2
    @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) – Honinbo Shusaku Sep 09 '16 at 14:46
  • 3
    @Abdul, dot sourcing is simpler, modules are much more powerful. See http://stackoverflow.com/questions/14882332/powershell-import-module-vs-dot-sourcing/14882447#14882447 – Michael Freidgeim Sep 09 '16 at 22:33
  • Modules are sweet! But what if I WANT to maintain/use a variable from the function further down in my script? is dot sourcing the way to go? – Eric Furspan Sep 17 '16 at 02:58
  • 2
    According to [this question](https://stackoverflow.com/questions/6412921/powershell-import-module-doesnt-find-modules), module files has to be placed in a folder with same name, such as `$modulesDir\MyFunctions\MyFunctions.psm1`. – Franklin Yu Aug 15 '18 at 18:11
  • From the current documentation, import a specific module like this: `Import-Module -Name $myModuleFilePath -Verbose`. You can add `-NoClobber` and other parameters. Doc link is: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/import-module?view=powershell-6 – Steven Coco Sep 17 '19 at 05:59
  • 2
    This worked for me `Import-Module "$PSScriptRoot\GetTacos.psm1" ` – Taco_Buffet Jan 11 '20 at 01:32
  • Without `.psm1` worked for me: `Import-Module $PSScriptRoot\GetTacos` – user182917 May 31 '21 at 19:18
22

. "$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).

Community
  • 1
  • 1
yzorg
  • 4,224
  • 3
  • 39
  • 57
  • FWIW, you don't have to do either of those things in order to run script in a module. – Nick Cox Feb 01 '18 at 08:35
  • @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). – yzorg Jun 06 '18 at 15:42
  • I frequently import the [FluentMigrator.PowerShell](https://www.nuget.org/packages/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. – Nick Cox Jun 12 '18 at 03:46
  • 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? – Nick Cox Jun 12 '18 at 03:52
  • @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. – yzorg Jun 12 '18 at 13:24
  • I don't follow. What wouldn't be found? – Nick Cox Jun 12 '18 at 23:55
  • 1
    I tried many combinations of ambersand, dot, double dot, with & without quotes, with and without leading slash, and using $PSScriptRoot was the only thing that worked for me - thanks – Greg Trevellick Aug 04 '21 at 19:27
  • @NickCox I'm talking about this step of the main answer: "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." -- This step is a pain for developers using multiple copies of scripts from GIT while working with multiple branches of the source code. This may no longer be needed, per 2016 comment from Michael Freidgeim. – yzorg Aug 09 '21 at 15:35
  • I don't think that has ever been required, has it? All you need to import a module from an arbitrary location is to know the path to that module. – Nick Cox Aug 10 '21 at 03:02
9

You can add function to:

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

An the function will be available.

The actual location of the profile file is determined by the $PROFILE variable and typing just that into a powershell terminal will tell you the file to edit.

Michael Malone
  • 618
  • 6
  • 18
David Morrow
  • 262
  • 4
  • 9
7

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!

Jonny
  • 2,663
  • 1
  • 24
  • 24
  • 1
    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" } – Jonny Jun 01 '13 at 11:38
6

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.

bergmeister
  • 949
  • 2
  • 10
  • 16
  • 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. :-) – bergmeister Feb 14 '18 at 22:01
3

Let's say I created a script at path C:\Temp\TestScript.sp1

enter image description here

Scripts and functions within those scripts follow the rules of scope.

enter image description here

Also When you use the call operator (&) to execute a script file in powershell editor, it is also not added to the current scope of powershell editor as shown below enter image description here

Hemendr
  • 673
  • 6
  • 12
1

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;
Bytekoder
  • 192
  • 1
  • 7
  • 23