83

(I can't believe I'm actually asking this, but I am out of brainpower for the day.)

I just wrote my first serious PowerShell script, and I'm really happy with it. I plan to use it every day or so. I want to be able to call it from the Posh command line. I'll give it a verb-noun type name, but for now it's a simple .ps1 and not one of those fancy advanced functions that take params and such.

So where should it go and how do I call it from a Posh command line? I plan to write more! Where should they go?

  • Should it be a function in my profile?
  • Should it go on my path?
  • Does it go in a PSMODULEPATH? What kind of stuff goes there anyway? Does it look recursively or is it just like the normal PATH?

Where do you all put your PowerShell scripts and how do you organize them? I've got a lot of experience making C# and C++ tools and know what to name them and where to put them. And at the other extreme I've done a lot of crappy .bat files which are usually standalone or piled in a heap in some folder. But PowerShell seems to be very different. You can make crappy .bat file type things in it really quickly, or you can be building libraries and sophisticated services with it.

I'd really love some ideas on how I should start organizing these things before I start. Obviously everybody is different, so I'm hoping for some discussion. Thanks!

1
  • 1
    You might want to reconsider the best solution based on all other answers provided lately. Commented Sep 1, 2023 at 9:35

7 Answers 7

31

I put my personal scripts in the same folder as my profile. I can then back up & version them together. My profile begins with:

$ProfileRoot = (Split-Path $script:MyInvocation.MyCommand.Path)
$env:path += ";$ProfileRoot"
Sign up to request clarification or add additional context in comments.

3 Comments

use $script:MyInvocation.MyCommand.Path, see source path of an executing script. You get into problems with nested callers, even with the profile script, for example I have a helper function to reload your profile (after function/variable edits) but skip all one-time init (like loading .NET assemblies).
Is this script idempotent? Or would you potentially be re-appending the same directory over and over each time you ran it?
@RossBrasseaux There is no checking if PATH already includes the $ProfileRoot inside before appending, so no, it's not idempotent in the current state.
21

My recommendations: - Store the script in a directory as you wish, e.g. c:\posh - Add the directory to $env:path

$env:path += ";c:\posh"

This ensures that you may be in other directory, say c:\windows, but you can call the script

[c:\windows] > sampl[TAB] # it expands the name of file to sample.ps1, then hit enter

If your file sample.ps1 contains functions definitions and you import it every time, then I would consider adding this line to your $profile file

. c:\posh\sample.ps1

Concerning script organization.. just several dirs according to the purpose of the scripts :) Personal, dev, external (downloaded), samples,...

1 Comment

This is the most straightforward answer, and it works. Plus it doesn't screw up your personal organization. As far as backups, etc. These are very custom, and so its case by case where you should store scripts for backup, etc. Plus "backup" isn't really pertinent to the question :).
19

With V2, you can create a modules directory in the WindowsPowerShell directory where your profile is. PS will automatically look in that directory to load modules when you run import-module. I created a "Scripts" directory under WindowsPowerShell as well that is a sibling directory of Modules.

I use my profile to set some directories using variables with the following code:

PS>  cat $Profile
$scripts = "$(split-path $profile)\Scripts"
$modules = "$(split-path $profile)\Modules"
$docs    =  $(resolve-path "$Env:userprofile\documents")
$desktop =  $(resolve-path "$Env:userprofile\desktop")

PS> cat variable:\scripts
C:\Users\andy.schneider\Documents\WindowsPowerShell\Scripts

PS>  cat variable:\modules
C:\Users\andy.schneider\Documents\WindowsPowerShell\Modules

2 Comments

This is what I do as well. Though I also have a "Libraries" directory with scripts that my profile auto executes to define global functions. I did that in PowerShell v1, I will probably replace all that with Modules eventually.
Note that in the example code, $scripts and $modules will be Strings, while $desktop and $docs will be PathInfo objects. This is because Split-Path always returns a String, while Resolve-Path usually returns a PathInfo object (unless used with -Relative, in which case it returns a String). For consistency, please consider either - have all variables be Strings (by adding .Path to the $docs and $desktop assignment expressions), or - have all variables be PathInfo objects (by wrapping the $scripts and $modules assignment expressions with $(Resolve-Path $(...)).
11

This is what I do:

Note: Substitute ModuleName for something meaningful.

Create a module and save it in the global modules folder as C:\Program Files\WindowsPowerShell\Modules\ModuleName\ModuleName.psm1. e.g.:

function global:FancyFunction() {
  # do something interesting here.
}

Export-ModuleMember -function FancyFunction 

Open your PowerShell profile script and add the following line to make sure that your module is loaded every time you start a PowerShell session:

Import-Module ModuleName -Force

You can easily find your PowerShell profile startup script by typing:

Notepad $PROFILE

Note: $PROFILE.AllUsersAllHosts still uses the legacy path of C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1

When you open a new PowerShell session, you should be able to call your function from the console or from other scripts without having to do anything else.

Note: You actually don't have to pre-load your modules using the $PROFILE script to be able to use the exported commands as later versions of PowerShell knows where to look.

1 Comment

C:\Windows\System32\WindowsPowerShell\v1.0\Modules is now a legacy path and shouldn't be used any more.
2

There is an exported variable in PowerShellGet module used in powershell 7.1

Import-Module PowerShellGet -RequiredVersion 2.2.5
$PSGetPath

it has 4 properties

AllUsersModules    : C:\Program Files\PowerShell\Modules
AllUsersScripts    : C:\Program Files\PowerShell\Scripts
CurrentUserModules : C:\Users\username\Documents\PowerShell\Modules
CurrentUserScripts : C:\Users\username\Documents\PowerShell\Scripts

you could use one of those script locations. Install-Script is using them also.

5 Comments

what is the correct name of the variable?
@VladislavBorovikov $PSGetPath is the correct variable, but it only exists in PWSH 7.x (don't know about PWSH 6.x).
It doesn't exist. Please provide the link to the official documentation describing this variable.
this variable is a part of external module that is responsible for package management Import-Module PowerShellGet; $PSGetPath
if you can upgrade older powershell runtime with latest version of PowershellGet (I use 2.2.5 version) it will be available even in Windows PowerShell 5. this variable is official it is specified in the module manifest ExportedVariables field
1

Normally you would distribute your code using advanced functions in modules. But if you for some obscure reason don't want to have your scripts to behave as built-in commands, you can distribute script files instead.

Scripts can be installed from a registered PSRepository (such as PS Gallery or one of your own, see Register-PSRepository) using the command Install-Script.

Install-Script -Repository PSGallery -Name Write-HelloWorld

You can select the installation directory to be used by specyfing -Scope

  • -Scope CurrentUser will install the script to
    $ENV:UserProfile\Documents\WindowsPowerShell\Scripts
  • -Scope AllUsers will install the script to
    $ENV:ProgramFiles\WindowsPowerShell\Scripts

For PowerShell 7.x the paths will be

  • -Scope CurrentUser will install the script to
    $ENV:UserProfile\Documents\PowerShell\Scripts
  • -Scope AllUsers will install the script to
    $ENV:ProgramFiles\PowerShell\Scripts

(The scripts folder doesn't exist for PowerShell 7 untill you try installing a script for the first time.)

Instead of using a PowerShell repository and Install-Script you could of course copy the script directly to the preferred script folder.

The $ENV:Path variable, of course, needs to include the path to the script folder.

The difference will be that Get-InstalledScript will only display scripts that got the corresponding XML-description file, copied to the folder InstalledScriptInfos, as part of Install-Script command usage.

2 Comments

Note that $ENV:ProgramFiles\WindowsPowerShell\Scripts isn't in the path until the first time install-script runs.
Also worth nothing that scripts can act like advanced functions too (despite the latter's name, there can also be advanced scripts), so there's no strict need to use a module for distributing a single command.
0

I took the route of ordering all my PowerShell pieces in one place...mostly for easier archiving. I put them in $env:APPDATA\Microsoft\Windows\PowerShell\. This is because this where the history is stored and I like these pieces in an out of the way place (to leave my Document folder just for documents).

I add the Modules path:

$env:PSModulePath += ";${env:USERPROFILE}\AppData\Roaming\Microsoft\Windows\PowerShell\Modules\"

Profile location redefine:

New-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" -Name "Personal" -PropertyType String -Value "$env:APPDATA\Microsoft\Windows" -Force
Stop-Process -Name explorer -Force

Add my scripts to Path and give them a handy shortcut:

$PATH_ADD = "$env:APPDATA\Microsoft\Windows\PowerShell\Scripts;"
$PATH_OLD = (Get-ItemProperty -Path HKCU:\Environment).Path
$PATH_NEW = $PATH_OLD + $PATH_ADD
Set-ItemProperty -Path HKCU:\Environment -Name Path –Value $PATH_NEW

function cds { Set-Location $env:APPDATA\Microsoft\Windows\PowerShell\Scripts }

And it looks like this:

d----          240610 Mon    10:31                Modules
d----          240610 Mon    10:51                PSReadLine
d----          240827 Tue    10:22                Scripts
-a---          240828 Wed    13:07          10608 Microsoft.PowerShell_profile.ps1
-a---          240905 Thu    10:29             55 powershell.config.json

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.