Scope of functions defined in modules when used in psake tasks - powershell

I have a psake Task looking something like below (this is simplified for clarity):
Task Invoke-Deploy {
Import-Module "somefunctions.psm1"
Import-Module "morefunctions.psm1"
Set-Something #This is a function defined in morefunctions.psm1
}
Function Set-Something (which is defined in module morefunctions.psm1) attempts to call function Get-Something (which is defined in somefunctions.psm1). When it does I get an error:
The term 'Get-Something' is not recognized as the name of a cmdlet,
function, script file, or operable program.
Interestingly I modified "morefunctions.psm1" to also 'Import-Module "somefunctions.psm1"' and at that point everything worked fine. I would rather not have to do this however as i want my modules to be "loosely-coupled" insofar as they don't need to rely on the existence of other modules.
My knowledge of function/variable scope in Powershell is limited but I thought that functions in two different imported modules lived in the same scope and hence a function in one of those modules would be able to call a function in the other.
I am suspecting that that scope is being affected by the fact that I'm inside a psake task, I'm hoping that someone here can confirm that and also advise on what I should do to fix this. TIA.

I created a script module test-module.psm1:
function Invoke-Test {
Import-Module ".\somefunctions.psm1"
Import-Module ".\morefunctions.psm1"
Set-Something #This is a function defined in morefunctions.psm1
}
and a couple of dummy modules, somefunctions.psm1:
function Get-Something {
'Get-Something'
}
and morefunctions.psm1:
function Set-Something {
Get-Something
'Set-Something'
}
If I call
Import-Module .\test-module.psm1
Invoke-Test
then I get the error "Get-Something : The term 'Get-Something' is not
recognized as the name of a cmdlet, function, script file, or operable
program.". So it looks like a generic PowerShell issue dealing with script
modules. I tried PowerShell v2.0, v3.0, and v4.0.
Perhaps this cannot be resolved in psake without workarounds because it is a script
module. You can use the similar tool
Invoke-Build. It is implemented
as a script and avoids issues like these. It works fine
with this build script:
Task Invoke-Deploy {
Import-Module ".\somefunctions.psm1"
Import-Module ".\morefunctions.psm1"
Set-Something #This is a function defined in morefunctions.psm1
}
It outputs, as expected:
Build Invoke-Deploy ...\.build.ps1
Task /Invoke-Deploy
Get-Something
Set-Something
Done /Invoke-Deploy 00:00:00.0150008
Build succeeded. 1 tasks, 0 errors, 0 warnings 00:00:00.1450083

I ran into this today and got it to work.
In your module "morefunctions.psm1" you need to export the method you want like this:
Export-ModuleMember -Function Set-Something
In your psake task, you need to prepend the module name in front of the method so PowerShell can find it:
Import-Module "morefunctions.psm1"
morefunctions\Set-Something

Related

Powershell function call causes missing function error using powershell v7 on windows 10

I wrote a script to build all .net projects in a folder.
Issue
The issue is I am getting a missing function error when I call Build-Sollution.
What I tried
I made sure that function was declared before I used it so I am not really sure why it saids that it is not defined.
I am new to powershell but I would think a function calling another functions should work like this?
Thanks in advance!
Please see below for the error message and code.
Error Message
Line |
3 | Build-Sollution $_
| ~~~~~~~~~~~~~~~
The term 'Build-Sollution' is not recognized as the name of a cmdlet, function, script file, or operable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Build-Sollution:
Code
param (
#[Parameter(Mandatory=$true)][string]$plugin_path,
[string]$depth = 5
)
$plugin_path = 'path/to/sollutions/'
function Get-Sollutions {
Get-ChildItem -File -Path $plugin_path -Include *.sln -Recurse
}
function Build-Sollution($solution) {
dotnet build $solution.fullname
}
function Build-Sollutions($solutions) {
$solutions | ForEach-Object -Parallel {
Build-Sollution $_
}
}
$solutions_temp = Get-Sollutions
Build-Sollutions $solutions_temp
From PowerShell ForEach-Object Parallel Feature | PowerShell
Script blocks run in a context called a PowerShell runspace. The runspace context contains all of the defined variables, functions and loaded modules.
...
And each runspace must load whatever module is needed and have any variable be explicitly passed in from the calling script.
So in this case, the easiest solution is to define Build-Sollution inside Build-Sollutions
As for this...
I am new to powershell but I would think a function calling another
functions should work like this?
... you cannot use the functions until you load your code into memory. You need to run the code before the functions are available.
If you are in the ISE or VSCode, if the script is not saved, Select All and hit use the key to run. In the ISE use F8 Selected, F5 run all. In VSCode, F8 run selected, crtl+F5 run all. YOu can just click the menu options as well.
If you are doing this from the consolehost, the run the script using dot sourcing.
. .\UncToYourScript.ps1
It's ok to be new, we all started somewhere, but it's vital that you get ramped up first. so, beyond what I address here, be sure to spend time on Youtube and search for Beginning, Intermediate, Advanced PowerShell for videos to consume. There are tons of free training resources all over the web and using the built-in help files would have given you the answer as well.
about_Scripts
SCRIPT SCOPE AND DOT SOURCING Each script runs in its own scope. The
functions, variables, aliases, and drives that are created in the
script exist only in the script scope. You cannot access these items
or their values in the scope in which the script runs.
To run a script in a different scope, you can specify a scope, such as
Global or Local, or you can dot source the script.
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. The functions, variables, aliases, and drives
that the script creates are created in the scope in which you are
working. After the script runs, you can use the created items and
access their values in your session.
To dot source a script, type a dot (.) and a space before the script
path.
See also:
'powershell .net projects build run scripts'
'powershell build all .net projects in a folder'
Simple build script using Power Shell
Update
As per your comments below:
Sure the script should be saved, using whatever editor you choose.
The ISE does not use PSv7 by design, it uses WPSv5x and earlier.
The editor for PSv7 is VSCode. If you run a function that contains another function, you have explicitly loaded everything in that call, and as such it's available.
However, you are saying, you are using PSv7, so, you need to run your code in the PSv7 consolehost or VSCode, not the ISE.
Windows PowerShell (powershell.exe and powershell_ise.exe) and PowerShell Core (pwsh.exe) are two different environments, with two different executables, designed to run side-by-side on Windows, but you do have to explicitly choose which to use or write your code to branch to a code segment to execute relative to the host you started.
For example, let's say I wanted to run a console command and I am in the ISE, but I need to run that in Pwsh. I use a function like this that I have in a custom module autoloaded via my PowerShell profiles:
# Call code by console executable
Function Start-ConsoleCommand
{
[CmdletBinding(SupportsShouldProcess)]
[Alias('scc')]
Param
(
[string]$ConsoleCommand,
[switch]$PoSHCore
)
If ($PoSHCore)
{Start-Process pwsh -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
Else {Start-Process powershell -ArgumentList "-NoExit","-Command &{ $ConsoleCommand }" -PassThru -Wait}
}
All this code is doing is taking whatever command I send it and if I use the PoSHCore switch...
scc -ConsoleCommand 'SomeCommand' -PoSHCore
... it will shell out to PSCore, run the code, otherwise, it just runs from the ISE>
If you want to use the ISE with PSv7 adn not do the shell out thing, you need to force the ISE to use PSv7 to run code. See:
Using PowerShell Core 6 and 7 in the Windows PowerShell ISE

Does PowerShell import function as module command by naming convention?

I have a PS module and in the .psm1 file for this module all relevant .ps1 files are "dot-sourced", or in my vocabulary: loaded. In one of the .ps1 files, let us say I have a function SetFoo.
If I do an Import-Module I cannot use the SetFoo function. If I list the commands for the module via Get-Command it is not available.
If I change the name of the function to Set-Foo the function is recognized after having done Import-Module and it is listed via Get-Command.
So my question is: is it via function naming convention that Set-Foo is loaded and SetFoo is not? I have googled myself silly, but cannot find anything in the PS docs about this.

Import-Module not working when called after a param() statement

I've a script which uses a parameter to pass details to it, and which needs to import the WebAdministration module.
The start of the script is :
param(
[parameter(position=0)]
[string]$iisAppName
)
Import-Module -name WebAdministration
however when I run the script I get errors from those cmdlets which use the module saying they're not found, since the module obviously hasn't been loaded.
If I put the Import-Module statement before the param() then the parameter isn't loaded. If I don't have the param() statement at all it works fine.
This script is for removing a website, but the companion creation script (which doesn't use param) works fine. In fact if I run that one it works, and if I then run this one (where the module is still loaded from the first) it works fine (annoyingly... since I didn't spot the issue in testing!), so I know I'm calling those cmdlets correctly.
Is there an alternate way I need to call one or both of these to allow both of them to work in my script?
I think this is to do with session states but would need more information to be sure.
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/import-module#-global
By default, the commands in a module, including commands from nested modules, are imported into the caller's session state.
When you import a module from the global session state, it's available to the console and all modules. When the module is imported from another module, it will only be available to the module(s) that imported it. I think when you include Params it treats it differently, perhaps running it in a script state session instead of the global state session.
Try using Import-Module -Name WebAdministration -Global which, regardless of where it is called, should import it into the global state session and make it available to everything.

shared function in script and code block

I have a function that I use in my main script, and I also need to create a Job, which uses the same function. Right now I just have the code written twice, once in the main script, and once in the script block handed to the Job. I know I can add code to a variable, but not sure how to then unpack that variable in the code block, so the same code is effectively used in both places.
If it makes a difference, I am limited to v2 unfortunately.
Use a Module
Define your function(s) in the module.
In your main script, you import the module.
Import-Module MyModule
In your job, you import the module:
Start-Job -ScriptBlock {
Import-Module MyModule
# Invoke-MyFunction
}

How can I use a PowerShell module in a script without leaving the module loaded in the user's session?

I have a script I wish to use interactively from the PowerShell prompt. The script needs to use a local script module.
I cannot see how to import/use the module such that it's not left loaded in the current session.
Example
A module (MyModule.psm1)...
function Test-Method
{
write-host "Test-Method invoked"
}
... and a script (script.ps1)
Import-Module .\MyModule
Test-Method
Now running the script at the PowerShell prompt ...
PS C:\temp> Get-Module | % {$_.Name}
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
PS C:\temp> .\script.ps1
Test-Method invoked
PS C:\temp> Get-Module | % {$_.Name}
Microsoft.PowerShell.Management
Microsoft.PowerShell.Utility
MyModule
How can my script import and use MyModule.psm1 without it being left loaded in the caller's current session? Bearing in mind that the call may have already imported the module and would not want it unloaded by the script (so simply removing the module at the completion of the script is not really good enough).
I've considered dot-sourcing the module rather than importing it, but I want the module for the reasons covered in PowerShell Import-Module vs Dot Sourcing
It sounds like you already described in pseudo-code what you wanted. Here it is in actual code:
$checkCmds = Get-Commands -Module MyModule
Import-Module MyModule
# Do stuff here . . .
# unload only if we loaded it
if ($checkCmds -eq $null) { Remove-Module MyModule }
As far as I can tell, you don't get that automatic cleanup behavior from a "script" importing a module. OTOH if you import a module from within another module, when the parent module is removed then any modules it imported will be removed if there are no other modules using them (or unless ipmo -global was specified).
This builds on the previous answer and uses the following property.
If you import a module from within another module, when the parent module is removed then any modules it imported will be removed
You can exploit several techniques to create a wrapper:
Importing a module from a module
Anonymous modules
Call operator with the context of a module
Set script.ps1 to
& (New-Module {
function Invoke-Function {
Import-Module .\MyModule
Test-Method
}
}) { Invoke-Function }
If you run script.ps1 and then (Get-Module).Name then MyModule will not be listed in the output.
Note: In this example Invoke-Function is just another scope, and can be omitted, letting the New-Module just run when defined. In one line:
& (New-Module { Import-Module .\MyModule; Test-Method }) {}
You can import the module with -Scope local to restrict a module to your script's scope. If the module happens to also be loaded in the global scope, then it will still be available after your script exits.