Declaring Module Parameters

This topic contains 7 replies, has 4 voices, and was last updated by Profile photo of Martin Nielsen Martin Nielsen 1 year, 8 months ago.

  • Author
    Posts
  • #23305
    Profile photo of Juli
    Juli
    Participant

    If I have a script or module that is importing another module with mandatory variables, can I declare those parameters before importing the module?

    If I have this module Sessions:

    function Load-Session
    {

    [CmdletBinding()]

    param(

    [Parameter(Mandatory=$True)]
    [string]$ExchConnection,

    [Parameter(Mandatory=$True)]
    [string]$LyncConnection

    )

    And I want to import it into another script, can I do this?

    function disable-employee

    {
    [CmdletBinding()]

    param(
    [Parameter(Mandatory=$True)]
    [string]$Path,

    [Parameter(Mandatory=$True)]
    [string]$EmailAddress,

    [Parameter(Mandatory=$True)]
    [string]$ExchangeAdd,

    [Parameter(Mandatory=$True)]
    [string]$LyncAdd

    )

    $ExchConnection = $ExchangeAdd
    $LyncConnection = $LyncAdd

    import-module Sessions

    Sessions

    }

  • #23306
    Profile photo of Don Jones
    Don Jones
    Keymaster

    So, let's get some terminology straight:

    The module doesn't have any parameters. The module contains a function, which defines parameters.

    And no, you can't do what you're asking, because functions don't work that way. You don't set a function's parameters by setting variables. You'd have to:

    Import-Module Sessions
    Load-Session -ExchConnection $ExchangeAdd -LyncConnection $LyncAdd
    

    Values are passed into functions as [i]parameters[/i]. Inside the function, those parameters are used the same as variables. But from outside the function, you have to specify them as parameters when you run the function. Until the function starts to run, those parameters don't exist, so there's no way to "pre-set" them.

    Now, there's another approach you can use. The module can – outside the body of a function – define variables. These are called module-level variables. Once the module is loaded (and not before), you can set those, and they'll retain their value. Functions within that module can use the module-level variables as default values for parameters (although those same parameters can't be Mandatory, if you want this to work). That would allow you to set the module-level variable, and then have it used by the module's functions as a default value for the duration of the module's life in the session.

  • #23307
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    The mandatory parameters you're showing are for a function within the module, rather than for the module itself. They must be passed to any call to Load-Session.

    In PowerShell v3 and later, you could make use of the PSDefaultParameterValues variable, if you wanted to set and forget those:

    $PSDefaultParameterValues['Load-Session:ExchConnection'] = $ExchangeAdd
    $PSDefaultParameterValues['Load-Session:LyncConnection'] = $LyncAdd
    
    Import-Module Sessions
    Load-Session
    
  • #23315
    Profile photo of Juli
    Juli
    Participant

    Thank you for your help. I actually got it working the way I want: This is the scenario. I have a termination script that takes a csv input and for each name in the csv, removes the user from all A.D. groups, removes the sip account and exports the active and archive email. Then for all successful exports, disables the mailbox. I created a module so it could be used by anyone in my group. This second module determines if the Active Directory module is loaded and if there are exchange and/or lync remote sessions loaded. If not it loads the necessary ones and if they are already loaded, it skips loading them.

    So here is what I ended up with:

    function disable-employee

    [CmdletBinding()]

    param(
    [Parameter(Mandatory=$True)]
    [string]$Path,

    [Parameter(Mandatory=$True)]
    [string]$EmailAddress,

    [Parameter(Mandatory=$True)]
    [string]$ExchangeAddress,

    [Parameter(Mandatory=$True)]
    [string]$LyncAddress

    )

    BEGIN
    {

    $ExchangeAddress
    $LyncAddress

    import-module MgmtSession

    Load-Session -ExchangeConnection $ExchangeAddress -LyncConnection $LyncAddress

  • #23337
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    Technically speaking you can define a global variable that's accessible from inside a module.

    #Script.ps1
    
    $global:LyncAddress = "http://.."
    $global:ExchangeAddress = "http://.."
    
    Import-Module Sessions
    Load-Session
    
    
    #Sessions.psm1
    
    function Load-Session {
        Write-Output $global:LyncAddress
        Write-Output $global:ExchangeAddress
    }
    

    However with that being said, this is extremely bad form and should never be done except to say that it is bad form, and that it should never be done. Why is it bad form? For one, it's terribly messy and you risk the code being modified outside the context of your module. Bad.

    On the other hand, even the best must sometimes resort to goto.

  • #23339
    Profile photo of Don Jones
    Don Jones
    Keymaster

    I personally wouldn't recommend that approach (as Martin indicates, it's a bad idea). However...

    If you define a variable in a module:

    $MyVariable = 'whatever'
    

    That [i]becomes[/i] a global variable since modules are loaded into the global scope. There's no reason to use $global. And, when you unload the module, the module-level variables get removed automatically. This is how you can create module-level "preference" variables. But there's no reason to use the $global modifier; that can really easily lead to a lot of scope confusion, and it can leave a messy global scope after the module is unloaded.

    You would need to load the module first, and then set any variables that the module defines – and that's a much cleaner, easier-to-debug, and easier-to-maintain approach, if your choice is to create preference variables. I'll note that the module will automatically export those module-lavel variables as "global" variables, unless you've explicitly used Export-ModuleMember in the module. If you did, then you must use Export-ModuleMember to also export the variables you want "exposed."

    As a general practice (there are exceptions, but not a ton of them), you want to try and avoid scope modifiers like $global. They make it easy to be messier, and messier makes debugging and maintaining harder.

    Sorry, Martin – totally not trying to beat up on you, I just wanted to offer an alternative that fits a little better with debug-and-maintain practices. Hope you don't mind. The $global thing has been giving me a lot of grief and clean-up work lately, and it's getting late on a Friday! And, of course, you already pointed out that it's a bad practice!

  • #23341
    Profile photo of Dave Wyatt
    Dave Wyatt
    Moderator

    I don't think variables are exported by default, but functions are. A soon as you use Export-ModuleMember, then you need to specify everything (including the functions.) In Don's example, that would look like this:

    Export-ModuleMember -Function * -Variable MyVariable # or -Variable *
    
  • #23352
    Profile photo of Martin Nielsen
    Martin Nielsen
    Participant

    No offense taken at all, but I have to agree with Dave. I've never seen a script-scope variable go global without being explicitly asked to. For example

    $TestModuleVariable = '"Script"-scope variable'
    
    function TestFunction {
        Write-Host "TestModuleVariable: $TestModuleVariable"
    }
    
    PS C:\Users\mni> Import-Module C:\Users\mni\AppData\Local\Temp\PowerShell\TestModule -Verbose
    VERBOSE: Loading module from path 'C:\Users\mni\AppData\Local\Temp\PowerShell\TestModule\TestModule.psm1'.
    VERBOSE: Importing function 'TestFunction'.
    
    
    $TestModuleVariable = '"Script"-scope variable going global thanks to Export-ModuleMember'
    
    function TestFunction {
        Write-Host "TestModuleVariable: $TestModuleVariable"
    }
    
    Export-ModuleMember -Function * -Variable *
    
    PS C:\Users\mni> Import-Module C:\Users\mni\AppData\Local\Temp\PowerShell\TestModule -Verbose
    VERBOSE: Loading module from path 'C:\Users\mni\AppData\Local\Temp\PowerShell\TestModule\TestModule.psm1'.
    VERBOSE: Importing function 'TestFunction'.
    VERBOSE: Importing variable 'TestModuleVariable'.
    

    As for the use of scope modifiers, I completely agree they tend to clutter up a script if used unnecessarily, but I don't see how you would otherwise pre-create and feed variables into a module without resorting to function parameters. On the other hand I can't really think of any good use case for such a scenario anyway, but it's an interesting thought experiment nonetheless.

    It's an interesting point you mention though, that if you do export the variable it becomes global, but only for the duration that the script is loaded. That never really occurred to me. I have always used functions to transport data in and out of modules. I will have to implement this functionality in a script somehow.

You must be logged in to reply to this topic.