Powershell implicit remoting called from another module file - powershell

We have a number of Powershell scripts that call "modules" with a load of reusable functions in. One function we have in a module sets up implicit remoting to our main DC and then imports the active directory module to the local session. I know this works as I have pasted the function into Powershell and it works. But when a script loads the module that contains the function it imports correctly but the cmdlets are not useable from the main script. I imagine this is something to do with scoping but I just can't see how to get this to work. For reference we cannout change the idea of calling modules with reusable functions in as there to many scripts that rely on it and it would be a big job to change it.
--Added for clarification from a comment below
I have a script, lets call it Script1, this script does some stuff but also loads some additional functions from other script modules using the following code
$Global:ICTSGVModuleLocation="\\server1\Scripts\Support Files\PS Modules\Global Modules"
get-childitem $Script:ICTSGVModuleLocation | foreach-object $_.name {Import-Module "$Global:ICTSGVModuleLocation\$_" -Global}
In one of the other script modules (lets call it script2) is some code to create an implicit remoting session and import an active directory module, code below
Function Global:ICTSGF.Adconnecter
{Try {$Script:connect = new-pssession -ComputerName "$Global:ICTSGVAdConnectorServer";
invoke-command -session $script:connect {import-module activedirectory -prefix cardiff};
Import-Module (Import-PSSession -session $Script:connect -module ActiveDirectory - AllowClobber) -Global}
Catch {ICTSGF.ScriptOutput "E2" "A4" "Loading Active Directory Module Failed"}
Finally {}}
In the main script (script1) i load the function by calling Global:ICTSGF.Adconnecter. This then loads the banner saying loading active Directory module and it appears to work. However when i attempt to run a command from the module imported it doesnt work
get-cardiffaduser $user
Thanks

Related

Call cmdlet directly without loading it via $profile

I created a cmdlet *.ps1 file with a single function Backup-Data that I load into my $profile with Import-Module Backup-Data.ps1.
Everytime I test it, I save the file and reopen the powershell console but this process is very tedious. So, I was wondering whether there is a way to call that directly from the console?
Unless you're defining new types in the Backup-Data.ps1 script, the following should suffice for restarting your testing in an interactive session:
Remove-Module Backup-Data -Force
Import-Module Backup-Data.ps1

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.

How does powershell handle transitive / nested module imports?

I always thought that doing Import-Module in my current powershell console will make the module available throughout the console.
Recently however I have learned that when doing Import-Module from within a module, things are getting complicated.
Compare:
Windows PowerShell Session State
...
Module Session State
Module session states are created whenever the module or one of its nested modules is imported into the session. When a module exports an element such as a cmdlet, function, or script, a reference to that element is added to the global session state of the session. However, when the element is run, it is executed within the session state of the module.
What I can observe, is:
# This is mod1.psm1
Import-Module -Force mod2.psm1
# end mod1.psm1
-
PS> Import-Module -Force mod1.psm1
... This will make the functions of mod2.psm1 available via mod1, when checked with Get-Module
PS> Import-Module -Force mod2.psm1
... This will make the functions of mod2 available via mod2.
I don't even fully understand the behavior here, but now comes the interesting part:
# This is mod1.psm1
function fn_load_in_mod1 {
Import-Module -Force mod2.psm1
# mod2 stuff can be used here
}
# end mod1.psm1
PS> Import-Module -Force mod1.psm1
PS> # But here, no mod2 stuff is available anymore!
I learned that to make the mod2 stuff available to my console, I need to:
# This is mod1.psm1
function fn_load_in_mod1 {
Import-Module -Force mod2.psm1 -Global
# mod2 stuff can be used here, and with the -Global switch also "outside"
}
# end mod1.psm1
But I can't make head nor tail of that session state stuff.
How am I supposed to do an Import-Module from within a module "correctly"?

Dynamically create a cmdlets/module in PowerShell

This is what I want to achieve and I can't find anything about this.
I want to create a remote module proxy, for a module that is available on a remote server.
I know how to work with remoting but I want something that creates cleaner script files.
To give an example. If I want to execute MyCmdlet from module MyModule on a remote computer I would do this
$block={
# Invoke the cmdlet from a module named MyCmdlet
MyCmdlet -Parameter1 -Parameter2
}
Invoke-Command -ComputerName "" -ScriptBlock $block
But I would like to land into something like this
Import-Module MyModuleRemote
MyCmdlet -ComputerName "" -Parameter1 -Parameter2
Please noticed that MyModule is not installed on my client machine.
I could re-write the module with Invoke-Command wrapper for each cmdlet but that is not the purpose. What I would like to do is remot-ify the MyModule by creating an proxy equal proxy per cmdlet and parameter. Even the Get-Help should work at least for the parameter composition.
I have a couple of ideas but I'm not sure if it is even possible.
Create a powershell module e.g. PSRemotify that will probe the module on the remote server and generate the code.
If I chose to write files to the file system then this should be possible, if I could do reflection on the cmdlets.
If I don't want to save files then I need to do everything in memory. Can I write a cmdlet's body in memory? Can I generate a string and import its embedded cmdlet?
Create a script that does 1.2.
My preference would be option 1.2. Very clean and without leaving any traces on the file system.
Any ideas? Has anybody tried something like already?
Conclusion after my investigation and answer from #Persistent13:
PowerShell offer this feature out of the box. it is known as IMPLICIT REMOTING. Before #Persistent13's answer I took the wrong part because I think it is interesting to share my experience, I've blogged about it.
import and use module from a remote server
It sounds like what your looking for is implicit remoting and is fairly simple to set up.
Please note I've taken the instructions for this from here.
To create an implicit session you would:
PS C:\> $foo = New-PSSession -ComputerName DC1
PS C:\> Import-Module -PSSession $foo -Name ActiveDirectory
PS C:\> Get-ADUser
The above would open a PowerShell session on the computer DC1, import the ActiveDirectory module on the remote computer, and the command is run against the remote computer while it appears to be executed locally.
It is also possible the prefix the implicitly imported modules in case a local module would conflict with the remote one.
PS C:\> $foo = New-PSSession -ComputerName DC1
PS C:\> Import-Module -PSSession $foo -Name ActiveDirectory -Prefix DC1
PS C:\> Get-DC1ADUser
The advantage of this is there is no need to remot-ify your module and so long as it is present on the remote computer and PowerShell remoting is allowed.
However one caveat of this method is that the type of the object returned will change.
Deserialized.Microsoft.ActiveDirectory.Management.ADUser when implicit remoting is used.
Microsoft.ActiveDirectory.Management.ADUser when run locally.
When I raised the question back then, I didn't know the concept of implicit remoting.
In practice you import a module using Import-Module but you also specify a session to a remote server with the -Session parameter.
For example to import Microsoft.PowerShell.Management from server $target="SERVER" execute this
$session=New-PSSession -ComputerName $target
Import-Module Microsoft.PowerShell.Management -Session $session
With this a proxy module is generated in the current session that offers the same cmdlets as the one specified. The proxy cmdlets automatically push the parameters to the remote session $session and capture its output. With Microsoft.PowerShell.Management implicitly imported from SERVER the Get-Service returns the services from SERVER.
You can really see the implementation of the proxy cmdlets by executing
$module=Get-Module Microsoft.PowerShell.Management
$module.Definition
It's at least interesting.

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.