Organizing Script Code – Calling Scripts from Other Script Files
Each one of us has our own style in the way we accomplish tasks, scripting is no different. My style is modular scripting; where I build libraries of functions (code blocks) and control script flow by calling functions in the order I choose. By maintaining a large library of user-defined functions (custom functions), I “snap-in” new functionality within my PowerShell scripts, with ease.
I’m also a firm believer that cleanliness is next to Script-Godliness. PowerShell scripts should be well written so that other administrators can easily understand and follow your code. Using a template, which I’ve provided below, is a great way to organize your scripts. Copy and paste the following template into notepad and name it PowerShellTemplate.ps1.
Script Template
#*=============================================
#* Script Name: [FILE_NAME]
#* Created: [DATE_MDY]
#* Author: Jesse Hamrick
#* Company: PowerShell Pro!
#* Email:
#* Web: http://www.powershellpro.com
#* Reqrmnts:
#* Keywords:
#*=============================================
#* Purpose:
#*
#*
#*=============================================
#*=============================================
#* REVISION HISTORY
#*=============================================
#* Date: [DATE_MDY]
#* Time: [TIME]
#* Issue:
#* Solution:
#*
#*=============================================
#*=============================================
#* FUNCTION LISTINGS
#*=============================================
#* Function:
#* Created: [DATE_MDY]
#* Author:
#* Arguments:
#*=============================================
#* Purpose:
#*
#*
#*=============================================
#*=============================================
#* SCRIPT BODY
#*=============================================
#*=============================================
#* END OF SCRIPT: [FILE_NAME]
#*=============================================
Function Template
#*=============================================
#* FUNCTION LISTINGS
#*=============================================
#* Function:
#* Created: [DATE_MDY]
#* Author:
#* Arguments:
#*=============================================
#* Purpose:
#*
#*
#*=============================================
With PowerShell I use two methods to organizing my scripts:
- Modular Scripting
- Calling Functions from other script files using dot sourcing
Both are valid approaches, it is the situation that will dictate which method you will use.
Using Modular Scripting
I’m going to take two PowerShell script examples from the “Microsoft Script Center” and show you how to create user-defined functions and call them from a script.
Say you want to inventory your system, gathering information about the Processor(s) and Disk(s) available on the system. From the “Microsoft Scripting Center” you find these two scripts.
List Processor Information:
$colItems = get-wmiobject -class “Win32_Processor” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Caption: ” $objItem.Caption
write-host “CPU Status: ” $objItem.CpuStatus
write-host “Current Clock Speed: ” $objItem.CurrentClockSpeed
write-host “Device ID: ” $objItem.DeviceID
write-host “L2 Cache Size: ” $objItem.L2CacheSize
write-host “L2 Cache Speed: ” $objItem.L2CacheSpeed
write-host “Name: ” $objItem.Name
write-host
}
List Physical Disk Properties:
$colItems = get-wmiobject -class “Win32_DiskDrive” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Description: ” $objItem.Description
write-host “Device ID: ” $objItem.DeviceID
write-host “Interface Type: ” $objItem.InterfaceType
write-host “Media Type: ” $objItem.MediaType
write-host “Model: ” $objItem.Model
write-host “Partitions: ” $objItem.Partitions
write-host “Size: ” $objItem.Size
write-host “Status: ” $objItem.Status
write-host
}
You could run each of the scripts one at a time, but that just doesn’t cut it in the “real world.” You could also do what I call writing an “ABC” script. This is were you start at the beginning and write your code as it would parse (line by line). Here is the example of what I mean:
$colItems = get-wmiobject -class “Win32_Processor” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Caption: ” $objItem.Caption
write-host “CPU Status: ” $objItem.CpuStatus
write-host “Current Clock Speed: ” $objItem.CurrentClockSpeed
write-host “Device ID: ” $objItem.DeviceID
write-host “L2 Cache Size: ” $objItem.L2CacheSize
write-host “L2 Cache Speed: ” $objItem.L2CacheSpeed
write-host “Name: ” $objItem.Name
write-host
}
$colItems = get-wmiobject -class “Win32_DiskDrive” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Description: ” $objItem.Description
write-host “Device ID: ” $objItem.DeviceID
write-host “Interface Type: ” $objItem.InterfaceType
write-host “Media Type: ” $objItem.MediaType
write-host “Model: ” $objItem.Model
write-host “Partitions: ” $objItem.Partitions
write-host “Size: ” $objItem.Size
write-host “Status: ” $objItem.Status
write-host
}
The example above, I just combined the two scripts. Below is the script output when parsed:

Image 6.0
“ABC” scripts are a viable solution when working with small tasks and should not be counted out. But, as your tasks become more complex, so do your PowerShell scripts. To deal with complexity issues, I adopted the “Modular” approach to writing scripts in PowerShell.
I’m going to take the scripts and create user-defined functions. A function allows you to make calls to blocks of code within your script. Here is how it works…
First, I need to convert each script into a function. A simple Function declaration would look like this: “Function <Name> { … }”
Here’s how it looks using my “FunctionTemplate.”
List Processor Information Function
# ===============================================
# FUNCTION LISTINGS
# ===============================================
# Function: ListProcessor
# Created: [09/17/2007]
# Author: Jesse Hamrick
# Arguments:
# ===============================================
# Purpose: Provides Processor information
#
#
# ===============================================
function ListProcessor {
$colItems = get-wmiobject -class “Win32_Processor” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Caption: ” $objItem.Caption
write-host “CPU Status: ” $objItem.CpuStatus
write-host “Current Clock Speed: ” $objItem.CurrentClockSpeed
write-host “Device ID: ” $objItem.DeviceID
write-host “L2 Cache Size: ” $objItem.L2CacheSize
write-host “L2 Cache Speed: ” $objItem.L2CacheSpeed
write-host “Name: ” $objItem.Name
write-host
}
}
List Disk Information Function
#*===============================================
#* FUNCTION LISTINGS
#*===============================================
#* Function: ListDisk
#* Created: [09/15/2007]
#* Author: Jesse Hamrick
#* Arguments:
#*===============================================
#* Purpose: Provides Disk Information
#*
#*
#*===============================================
Function ListDisk {
$colItems = get-wmiobject -class “Win32_DiskDrive” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Description: ” $objItem.Description
write-host “Device ID: ” $objItem.DeviceID
write-host “Interface Type: ” $objItem.InterfaceType
write-host “Media Type: ” $objItem.MediaType
write-host “Model: ” $objItem.Model
write-host “Partitions: ” $objItem.Partitions
write-host “Size: ” $objItem.Size
write-host “Status: ” $objItem.Status
write-host
}
}
What I’ve done is encapsulated the Microsoft PowerShell Scripts into a simple Function declaration. I recommend saving your functions in a folder, called something like “Code Library” or “Function Library.” It will not surprise you how often you will be using the same code blocks for different scripts. These code blocks are the “modules” that snap into your scripts. Now I’m going to show you how…
Building a Modular PowerShell Script
Using the PowerShell template, I’m going to write a script that enumerates the processor(s) and disk(s) properties. I’ll “snap-in” my newly created functions and use the “SCRIPT BODY” section of the template to control the function calls. This may sound familiar to you if you have worked with VBScript; subroutines and functions. Couple of differences:
- Functions in PowerShell provide the same functionality that both subroutines and functions did in VBScript. So, subroutines do not exist in PowerShell.
- PowerShell parses scripts sequentially – meaning functions need to be declared before they can be called in the script. This is unlike VBScript, were the whole script is read by the scripting host before it is executed.
Here is what a modular script would look like:
#*=============================================
#* Script Name: [Computer Inventory]
#* Created: [09/15/2007]
#* Author: Jesse Hamrick
#* Company: PowerShell Pro!
#* Email:
#* Web: http://www.powershellpro.com
#* Reqrmnts:
#* Keywords:
#*=============================================
#* Purpose: Computer Invetory of CPU and Disk Properties
#*
#*
#*=============================================
#*=============================================
#* REVISION HISTORY
#*=============================================
#* Date: [DATE_MDY]
#* Time: [TIME]
#* Issue:
#* Solution:
#*
#*=============================================
#*=============================================
#* FUNCTION LISTINGS
#*=============================================
# Function: ListProcessor
# Created: [09/17/2007]
# Author: Jesse Hamrick
# Arguments:
# =============================================
# Purpose: Provides Processor information
#
#
# =============================================
function ListProcessor {
$colItems = get-wmiobject -class “Win32_Processor” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Caption: ” $objItem.Caption
write-host “CPU Status: ” $objItem.CpuStatus
write-host “Current Clock Speed: ” $objItem.CurrentClockSpeed
write-host “Device ID: ” $objItem.DeviceID
write-host “L2 Cache Size: ” $objItem.L2CacheSize
write-host “L2 Cache Speed: ” $objItem.L2CacheSpeed
write-host “Name: ” $objItem.Name
write-host
}
}
#*=============================================
#* Function: ListDisk
#* Created: [09/15/2007]
#* Author: Jesse Hamrick
#* Arguments:
#*=============================================
#* Purpose: Provides Disk Information
#*
#*
#*=============================================
Function ListDisk {
$colItems = get-wmiobject -class “Win32_DiskDrive” -namespace “root\CIMV2″ `
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Description: ” $objItem.Description
write-host “Device ID: ” $objItem.DeviceID
write-host “Interface Type: ” $objItem.InterfaceType
write-host “Media Type: ” $objItem.MediaType
write-host “Model: ” $objItem.Model
write-host “Partitions: ” $objItem.Partitions
write-host “Size: ” $objItem.Size
write-host “Status: ” $objItem.Status
write-host
}
}
#*==============================================
#* SCRIPT BODY
#*==============================================
# Create a string variable using the local computer.
$strComputer = “.”
# Call the “ListProcessor” function.
ListProcessor
# Call the “ListDisk” function.
ListDisk
#*==============================================
#* END OF SCRIPT: [Computer Inventory]
#*==============================================
Now you have a visual of a modular PowerShell script. I declared the functions and used the “SCRIPT BODY” to snap the functions in place. Let’s say you want to change to order of the script, you want to list the Disk Properties before the Processor Properties. You only need to reverse the order of the function calls in the “SCRIPT BODY” to make this happen.
Modular scripting is a great way to organize your scripts. It allows you to build from a user-defined functions library, that you maintain. You can “snap-in” new functionality to existing scripts as needed and easily control the flow of your script without having to move around large chunks of code. Modular scripting may not be the best solution for the example above, the example was provided only to introduce the concept. Again, as your tasks become more complex so do your scripts. With modular scripting, complex scripts become more manageable.
As you can see the modular script approach look like a lot of work when compared to using the dot source method, as you will see.
dot Sourcing
I want to thank Kevin(see his comment below) and give examples of what he is discussing, as another viable way to call items from your script library. Essentially, “dot-source” means using dot-notation to call script blocks, functions, and/or aliases from within your script. We’re going to use the same script examples above, but instead of calling the function written within the script, we will use dot-notation to call other scripts that exists outside the main script. The syntax used for dot-notation and script-blocks is:
Example 1.
In the first example, I’m going to be working from the “C:\MyScripts” directory.
Step 1. Save the following code as CPU.ps1 in the MyScripts directory:
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Caption: ” $objItem.Caption
write-host “CPU Status: ” $objItem.CpuStatus
write-host “Current Clock Speed: ” $objItem.CurrentClockSpeed
write-host “Device ID: ” $objItem.DeviceID
write-host “L2 Cache Size: ” $objItem.L2CacheSize
write-host “L2 Cache Speed: ” $objItem.L2CacheSpeed
write-host “Name: ” $objItem.Name
write-host
}
Step 2. Save this code as Disk.ps1:
-computername $strComputer
foreach ($objItem in $colItems) {
write-host “Description: ” $objItem.Description
write-host “Device ID: ” $objItem.DeviceID
write-host “Interface Type: ” $objItem.InterfaceType
write-host “Media Type: ” $objItem.MediaType
write-host “Model: ” $objItem.Model
write-host “Partitions: ” $objItem.Partitions
write-host “Size: ” $objItem.Size
write-host “Status: ” $objItem.Status
write-host
}
Steps 1 and 2 are the same script blocks that we’ve worked with in this PowerShell Article. Now, let’s modify our main script body to use dot-notation to call these scripts.
Step3: Modify the main script as follows: Remove the function(s) code from the script and modify the script body to call both CPU.ps1 and Disk.ps1 scripts.
#*================================================
#* Script Name: [Computer Inventory]
#* Created: [09/15/2007]
#* Author: Jesse Hamrick
#* Company: PowerShell Pro!
#* Email:
#* Web: http://www.powershellpro.com
#* Reqrmnts:
#* Keywords:
#*================================================
#* Purpose: Computer Invetory of CPU and Disk Properties
#*
#*
#*================================================
#*================================================
#* REVISION HISTORY
#*================================================
#* Date: [10/09/2007]
#* Time: [9:30 AM]
#* Issue: Dot-Source Example
#* Solution:
#*
#*================================================
#*================================================
#* SCRIPT BODY
#*================================================
# Create a string variable using the local computer.
$strComputer = “.”
# Use Dot-Source to Call the “ListProcessor” function.
.{.\CPU.ps1}
# Use Dot-Source to Call the “ListDisk” function.
.{.\Disk.ps1}
#*================================================
#* END OF SCRIPT: [Computer Inventory]
#*================================================
Since I’m working from “C:\MyScripts” directory I can use “.\CPU.ps1″ to call the scripts in my code. Run the script:
This works great if we organize all of our scripts (code library) within the “MyScripts” directory. Kevin also mentions the System Path to find the .ps1 files. The $PsHome variable provides the installation path of PowerShell. By saving our scripts (code library) to this directory, we only have to call the script name without possible path issues. By default, PowerShell is installed using the following directory path: C:\WINDOWS\system32\WindowsPowerShell\v1.0
Example 2.
Step 1. Let’s move ComputerInv_v1.1.ps1, CPU.ps1, and Disk.ps1 to the v1.0 directory. Then modify the script body as follows:
#*=================================================
#* Script Name: [Computer Inventory]
#* Created: [09/15/2007]
#* Author: Jesse Hamrick
#* Company: PowerShell Pro!
#* Email:
#* Web: http://www.powershellpro.com
#* Reqrmnts:
#* Keywords:
#*==================================================
#* Purpose: Computer Invetory of CPU and Disk Properties
#*
#*
#*==================================================
#*==================================================
#* REVISION HISTORY
#*==================================================
#* Date: [10/09/2007]
#* Time: [9:30 AM]
#* Issue: Dot-Source Example
#* Solution:
#*
#*==================================================
#*==================================================
#* SCRIPT BODY
#*==================================================
# Create a string variable using the local computer.
$strComputer = “.”
# Use Dot-Source to Call the “ListProcessor” function.
.{CPU.ps1}
# Use Dot-Source to Call the “ListDisk” function.
.{Disk.ps1}
#*==================================================
#* END OF SCRIPT: [Computer Inventory]
#*==================================================
Here is the change in the script body – from .{.\scriptname.ps1} to .{scriptname.ps1}
Step 2. With the script files being placed in the PowerShell install directory, we only have to type in the script name in which to run. The system path will find the where the scripts are located and launch them. Let’s launch the Computer Inventory script:
Now, CD to the root of C: drive and attempt to run the script again:
Did the script work? As Kevin commented, “it will use the System’s path to find the .ps1 files if necessary.”
Calling Functions that live in other script files
Using “dot-sourcing” you are able to call functions that live within a script file. For example, let’s say you have one large file that contains over 100 separate functions you’ve created. Essentially, you maintain your function library in one or two documents. You can use dot sourcing to read the file and call any one of the functions written in the file. Click here to find out how…
Which process should I use?
Do I write functions within the script or do I call other scripts outside the main script? There is really no wrong or right here, I mentioned that it depends on the situation. For example, let’s say you are writing a script for a client that will be utilizing functions in your script library. You may wish to use the modular approach so that you can present your client will a single script that is well document. If your writing scripts for your own environment, chances are your using dot sourcing to call other functions as it takes less time and less code to accomplish the task…
Email This Post To A Friend
« Windows PowerShell : The Definitive Guide | Home | We Don’t Need No Stinkin’ Scripts… »
Comments
I would recommend that instead of having a large number of snippets that you actually insert into the main body of the script, that you dot source the functions (it will use the System’s path to find the .ps1 files if necessary). This way the size of the individual script stays down and you can update/optimize your functions and all of the scripts calling them will immediately benefit.
Thanx !
Kevin, thats a superb tip, exactly what I needed, thanks!
This is how to do what Kevin is describing…
http://www.powershellpro.com/function-calling/144/
People, this doesn’t work! I’m trying to work on something completely different but it needs to utilize functions in different ps1 files and it doesn’t work. So I decided that I’m doing something wrong and I’m copying/pasting the entire code as displayed here and Powershell still cannot find those functions. You have my email if you care to help me ’see the light’ or if you want to ’see the light’ yourselves.
Is there a way to loop through subfolders and call a script named run.ps1 from below each folder. I’m fairly new to powershell and can’t get this to work. As presently written it simply treats the file as a literal string, not really what I’m after.
I want to place a script and all supporting files in a subfolder and then loop through each subfolder and call run.ps1. This is what I presently do with CMD files, that way when I no longer need a module we just delete the folder. Similarly if I need to add a module I just drop in a new folder. I don’t want to edit the calling script, if I can avoid it.
Here’s what I have:
$list = Get-ChildItem C:\APPS\SCRIPTS -exclude maintenance.ps1 | Sort-Object Name
foreach ($i in $list)
{
Write-Host “Processing ” $i”…”
$sScript = [STRING]$i + “\run.ps1″
# this line works
.{c:\apps\scripts00_Critical\run.ps1}
# this line doesn’t work
.{$sScript}
}
Hello Mr. Trotter,
I am looking out for exactly what you have asked for. Did you find the solution to this further. Is there anything like shell object.Run “$sScript” as we have in vbscript.
Please put down the solution if you have found one.
Mr. Trotter,
I got the solution. Incase if anybody is looking for it.
invoke-expression $variable-name
Great article got me upto speed using dot-sourcing.
Found an issue though using this method as I wanted to pass a path via a variable but it would fail.
. {“$scriptpath\runme.ps1″}
Wouldn’t work with or without quotes, but found a simple solution which was to remove the braces.
. $Scriptpath\runme.ps1
Pretty obvious I guess but it stumped me for awhile so thought others may benefit.
Thx
Mark
Cheers
Mark
Leave a Comment