How to Write Interdependent Scripts

How to Write Interdependent Scripts

  • Comments 6

A lot of the time, I want to build scripts that depend on other scripts that I’ve already written.  Sometimes I will just want to be able to use a custom Cmdlet or function I’ve written.  Sometimes, I’ll simply want to always call the underlying script without changing anything and sometimes I’ll want to change to make the default arguments to a command customizable (like giving a custom Copy-Item script a default destination).

 

There are two ways you can approach this.  You can reference the script, by .\ or by absolute path, and pass it’s arguments normally, or you can determine the location of the current script and create aliases or functions that call the script.  Of the two ways to go, the second, while a little trickier, is the recommended approach.

 

The first approach has a couple of drawbacks.  If you use .\ to run the scripts, then they have to really be in the current directory.  If you use this approach, you would have to switch the location where the scripts are (with Push-Location) just before you run the script, and then revert it when you’re done.  This approach works for making one command that depends on another, but if you tried to have another command that used that command, it wouldn’t work (because the new scripts would change the location, and then your script would change the location, and then the new scripts would be looking for scripts in the wrong place).  It is not a sustainable approach.

 

If you ever write production scripts that call other scripts with .\, you should try the approach below instead, because your scripts can conflict with others that take the same approach, or can have trouble working if scripts are called from another directory.

 

The second approach is pretty simple, and can be done in 3 steps:

 

1.       Find the root location of the script

2.       Set an Alias or create a function to call the script that the script depends on

3.       In each of the script files, check for all of the required commands before running

 

I’ll demonstrate the technique below.  In this case, I’m writing a script that depends on two other scripts, Get-MyData –Id <int> -DataSet<Object> and Set-MyData –Id <int> -DataSet<Object>, which depend on a third and fourth command, Test-MyData (used by both) and Add-MyData (used by just Set-MyData)

 

Part 1 can be done in this simple line, which should be at the top of the script (not within a function or script block)

      $scriptRoot = Split-Path (Resolve-Path $myInvocation.MyCommand.Path)

 

To do part 2:

 

If you don’t want to change the arguments you’re passing, you can just alias the script:

 

Set-Alias “Set-MyData” $scriptRoot\Set-MyData.ps1

 

Now let’s suppose I’ll almost always want to get the same set of data.  I can omit that parameter by writing a function that uses a different default and calls Get-MyData

 

function Get-MyData($id, $dataSet = “MyDataSet”) {

   $scriptRoot\Get-MyData –id $id `

     -dataSet $dataSet

}

 

To do part 3, you would put this little snippet of code after the parameter block in Set-MyData.ps1.  In Get-MyData.ps1, $requiredCommands would just be “Test-MyData”.

 

$requiredCommands = “Test-MyData”, “Add-MyData”

$null = get-command $requiredcommands -ea SilentlyContinue -ev missingRequiredcommands

foreach ($cmd in $missingRequiredCommands) {

    Write-Warning “Missing $($cmd.Exception.CommandName)”

}

if ($missingRequiredCommands.Count) { throw “Missing Required Commands, Exiting.” }

 

By following the approach outlined above (finding the root, aliasing the dependent scripts, and checking for dependencies in each script), you can reuse your PowerShell code quickly and easily.

 

Hope this helps,

 

James Brundage [MSFT]

Leave a Comment
  • Please add 2 and 6 and type the answer here:
  • Post
  • And I almost forgot the beginners events, not much special here : Event 3 d ir c:\scripts\*.txt |% {(get-content

  • Thanks for writing this.  I am trying to understand what you are saying.  So first I want to understand the problem you are trying to solve.  Is it that you can't run scripts in the current directory without an explicit path?

    Shouldn't you have a directory for your scripts?  And shouldn't that directory be on the path?  Wouldn't that fix this?

  • The problem I am trying to solve is how to write your scripts in a way that they can work well in relative paths.  I do have a script directory, but I also have many subdirectories beneath this directory.  I do not want each script that I write to be fully self contained, because I will have to write more code and because copying/pasting code will inevitably lead to differences as the code in the original files changes.

  • I have been struggling with this as well, and came up with a similar approach.  Part of my trouble is that I have multiple subdirectoies beneath my root scripts directory.  So I wrote a function that will locate script/binary dependencies and throws terminating errors if it cannot find those specified:

    function Get-ResourcePath {

       param(

           [string]$filename = $(Throw '-filename must be specified!'),

           [string]$srchPath = $(Split-Path $MyInvocation.Scriptname),

           [int]$srchUp = 0)

       $fileNotFound = $true

       for ( $i = $srchUp ; $i -gt -1 ; $i-- ) {

           $Result = @(Get-ChildItem $srchPath $filename -recurse)

           if ($Result[0] -ne $null) {

               Write-Host "Necessary resource $filename found at $($Result[0].fullname)"

               #Return the full path to the file being searched for.

               @($Result)[0].fullname

               $fileNotFound = $false

               break

           }

           $srchPath = Split-Path $srchPath

       }

       if ($fileNotFound) {

           Throw "Necessary Resource: $Filename not Found!"

       }

    }

    For production scripts I have defined the root in the global profile as $PSDAdminscriptroot and root my searches for dependencies there.

    I'm always looking to improve the way I search for dependencies, any comments/ideas on my searching script are appreciated.

    Thanks for posting your solution, adding aliases is something I'll have to incorporate into our scripts here.

  • What are the differences between interdependent scripts versus implementing your logic in functions and scripted cmdlets, and then kick starting things with scripts?

  • Note to self:

    See if this have anything to do with "dotting" a script as described in "Windows PowerShell in Action", chapter 7, page 210.

    script1.ps1

    --------------

    function doit($what)

    {

    write-host "done $what"

    }

    --------------

    script2.ps1

    --------------

    # call script1 to create the function

    (dir function:/).Count

    . './script1.ps1'

    (dir function:/).Count

    #now use it

    doit 'whatever'

    --------------

Page 1 of 1 (6 items)