Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PowerShell dot source within a dot sourced file - import classes

My project is structured like this:

MyScript.ps1
classes\
    Car.ps1
    Tesla.ps1

Car.ps1 is the base class of Tesla.ps1. I attempt to define Tesla like this in Tesla.ps1:

. "$PSScriptRoot\Car.ps1"

class Tesla : Car
{
}

MyScript.ps1 needs to use the Tesla class, but shouldn't need to know that it inherits from Car.

. "$PSScriptRoot\classes\Tesla.ps1"

$tesla = [Tesla]::new()

Dot sourcing to classes\Tesla.ps1 works fine, but this error is thrown from the Tesla file:

Unable to find type [Car]

If I import all the files in the correct order in MyScript.ps1, it works fine. Example:

. "$PSScriptRoot\classes\Car.ps1"
. "$PSScriptRoot\classes\Tesla.ps1"

$tesla = [Tesla]::new()

This is cumbersome, especially as the complexity grows. Am I dot sourcing incorrectly? Is there a better way to import a custom PowerShell class using a relative path that isn't in the PSModulePath?

like image 929
Jonathan Eckman Avatar asked Jul 31 '18 22:07

Jonathan Eckman


People also ask

What is Dot sourcing in PowerShell?

The dot sourcing feature lets you run a script in the current scope instead of in the script scope. When you run a script that is dot sourced, the commands in the script run as though you had typed them at the command prompt.

How do I import a class in PowerShell?

Import Classes using Using Module Statement The assembly-importing feature is a convenience; the module-importing feature is a requirement, because there's no other way to import classes from script modules. The Using statement has a module parameter that takes a module name string or a ModuleSpecification object.

What does source mean in PowerShell?

The PowerShell dot-source operator brings script files into the current session scope. It is a way to reuse script. All script functions and variables defined in the script file become part of the script it is dot sourced into. It is like copying and pasting text from the script file directly into your script.


1 Answers

PetSerAl, as countless times before, has provided the crucial pointers in a terse comment on the question:

Due to your use of classes, you must use modules and import them with using module statements in order to use them.

Unfortunately, as of this writing, using module is still not mentioned in Get-Help about_Modules).

Specifically, in order to reference a type (class) in a class definition, that type must be known to PowerShell at parse time.

In your example, in order to derive Tesla from class Car, type Car must be known to PowerShell when the script is parsed, before execution begins - that's why it is too late to try to import Car by dot-sourcing its containing script (. "$PSScriptRoot\Car.ps1")

PowerShell knows about referenced types at parse time in one of two ways:

  • If the type is already loaded into the current PowerShell session

  • Via a using module statement that references a module in which the type (class) is defined (note that there's also a using assembly statement for loading types from DLLs, and using namespace to enable referring to types by their mere names).

    • Note that the related Import-Module cmdlet does not work in this case, because it executes at runtime.

Therefore, as PetSerAl suggests:

  • Store your classes in module files (stand-alone *.psm1 files in the simplest case)

  • Then use using module to have the Tesla module import the Car module and the MyScript.ps1 script import the Tesla module, using paths that are relative to the enclosing script / module's location.

MyScript.ps1
classes\
    Car.psm1    # Note the .psm1 extension
    Tesla.psm1  # Ditto

Car.psm1:

class Car {}

Tesla.psm1:

using module .\Car.psm1

class Tesla : Car {}

MyScript.ps1:

using module .\classes\Tesla.psm1

$tesla = [Tesla]::new()
like image 124
mklement0 Avatar answered Nov 15 '22 05:11

mklement0