Using Module and path - powershell

I am running into some issues with loading a PSM1 file. The PSM1 is always in the same folder as the PS1, but that folder can change. It works if I use a literal path like this...
Using module '\\Mac\iCloud Drive\Px Tools 4.#\Dev 4.0\#Spikes\Windows7\library.psm1'
But that's useless since the code could be installed anywhere. And the code is signed, so it can't change.
It also doesn't work to use the new (in PS3.0) $PSScriptRoot automatic variable...
Using module "$PSScriptRoot\library.psm1"
Nor does a relative path or simple file name, as in...
Using module ".\library.psm1"
or...
Using module "library.psm1"
What am I missing, other than perhaps it's time to call it a day?
Also, note that the library contains classes, so other module loading options like Import-Module don't work. This has me wondering if perhaps classes are better supported in a later version, and I should really be targeting PS 6.0, rather than 5.1?

Looking for more info on your problem, I came around this blog post. It says relative paths work, so have you tried using relative path with single quotes or without quotes?
Since values inside double-quotes get evaluated before passing them to the cmdlet, using them might not work.

you should run import-module first.
in PS1, I have add code to call import-module
like this:
ForEach($_ in Get-ChildItem "$env:TEMP\*.psm1") {
$checkModuleName = $_.Name -replace ".psm1"
$importModule = $_.Name
if (Get-Module $checkModuleName) {
Write-Host "Update Module $importModule" -ForegroundColor Green
Remove-Module $checkModuleName
Import-Module "$env:TEMP\$importModule"
}
else {
Write-Host "Import Module $importModule" -ForegroundColor Green
Import-Module "$env:TEMP\$importModule"
}
}
when import done, I can used all module.

Related

PowerShell ignoring Write-Verbose while running Import-Module

For presenting the problem, I have this simple script saved as PowerShell module (test.psm1)
Write-Verbose 'Verbose message'
In real life, it includes command to import additional functions, but that is irrelevant at the moment.
If I run Import-Module .\test.psm1 -Verbose -Force I get only
VERBOSE: Loading module from path 'C:\tmp\test.psm1'.
My Write-Verbose is ignored 😟
I tried adding cmdletbinging but it also did not work.
[cmdletbinding()]
param()
Write-Verbose 'Verbose message'
Any clue how to provide Verbose output while importing the PowerShell module?
P.S. I do not want to display Verbose information always, but only if -Verbose is specified. Here would be my expected output for these two different cases:
PS C:\> Import-Module .\test.psm1 -Verbose -Force # with verbose output
VERBOSE: Loading module from path 'C:\tmp\test.psm1'.
VERBOSE: Verbose message
PS C:\> Import-Module .\test.psm1 -Force # without verbose output
PS C:\>
That is an interesting situation. I have a theory, but if anyone can prove me wrong, I would be more than happy.
The short answer: you probably cannot do what you want by playing with -Verbose only. There may be some workarounds, but the shortest path could be setting $VerbosePreference.
First of all, we need to understand the lifetime of a module when it is imported:
When a module is imported, a new session state is created for the
module, and a System.Management.Automation.PSModuleInfo object is
created in memory. A session-state is created for each module that is
imported (this includes the root module and any nested modules). The
members that are exported from the root module, including any members
that were exported to the root module by any nested modules, are then
imported into the caller's session state. [..] To send output to the host, users should run the Write-Host cmdlet.
The last line is the first hint that pointed me to a solution: when a module is imported, a new session state is created, but only exported elements are attached to the global session state. This means that test.psm1 code is executed in a session different than the one where you run Import-Module, therefore the -Verbose option, related to that single command, is not propagated.
Instead, and this is an assumption of mine, since I did not find it on the documentation, configurations from the global session state are visible to all the child sessions. Why is this important? Because there are two ways to turn on verbosity:
-Verbose option, not working in this case because it is local to the command
$VerbosePreference, that sets the verbosity for the entire session using a preference variable.
I tried the second approached and it worked, despite not being so elegant.
$VerbosePreference = "Continue" # print all the verbose messages, disabled by default
Import-Module .\test.psm1 -Force
$VerbosePreference = "SilentlyContinue" # restore default value
Now some considerations:
Specifying -Verbose on the Import-Module command is redundant
You can still override the verbosity configuration inside your module script, by using
Write-Verbose -Message "Verbose message" -Verbose:$false
As #Vesper pointed out, $false will always suppress the Write-Verbose output. Instead, you may want to parameterized that option with a boolean variable assigned in a previous check, perhaps. Something like:
if (...)
{
$forceVerbose=$true
}
else
{
$forceVerbose=$false
}
Write-Verbose -Message "Verbose message" -Verbose:$forceVerbose
There might be other less invasive workarounds (for instance centered on Write-Host), or even a real solution. As I said, it is just a theory.
Marco Luzzara's answer is spot on (and deserves the bounty in my opinion) in regards to the module being run in its own session state, and that by design you can't access those variables.
An alternative solution to setting $VerbosePreference and restoring it, is to have your module take a parameter specifically for this purpose. You touched on this a little bit by trying to add [CmdletBinding()] to your module; the problem is you have no way to pass in named parameters, only unnamed arguments, via Import-Module -ArgumentList, so you can't specifically pass in a $true for -Verbose.
Instead you can specify your own parameter and use it.
(psm1)
[CmdletBinding()]param([bool]$myverbose)
Write-Verbose "Message" -Verbose:$myverbose
followed with:
Import-Module test.psm1 -Force -ArgumentList $true
In the above example, it would apply only to a specific command, where you were setting -Verbose:$myverbose every time.
But you could apply it to the module's $VerbosePreference:
[CmdletBinding()]param([bool]$myverbose)
$VerbosePreference = if ($myverbose) { 'Continue' } else { 'SilentlyContinue' }
Write-Verbose "Message"
That way it applies throughout.
At this point I should mention the drawback of what I'm showing: you might notice I didn't include -Verbose in the Import-Module call, and that's because, it doesn't change the behavior inside the module. The verbose messages from inside will be shown purely based on the argument you passed in, regardless of the -Verbose setting on Import-Module.
An all-in-one solution then goes back to Marco's answer: manipulating $VerbosePreference on the caller's side. I think it's the only way to get both behaviors aligned, but only if you don't use -Verbose switch on Import-Module to override.
On the other hand, within a scope, like within an advanced function that can take -Verbose, setting the switch changes the local value of $VerbosePreference. That can lead us to wrap Import-Module in our own function:
function Import-ModuleVerbosely {
[CmdletBinding()]
param($Name, [Switch]$Force)
Import-Module $Name -Force:$Force
}
Great! Now we can call Import-ModuleVerbosely test.psm1 -Force -Verbose. But... it didn't work. Import-Module did recognize the verbose setting but it didn't make it down into the module this time.
Although I haven't been able to find a way to see it, I suspect it's because the variable is set to Private (even though Get-Variable seems to say otherwise) and so that value doesn't make it this time. Whatever the reason.. we could go back to making our module accept a value. This time let's make it the same type for ease of use:
(psm1)
[CmdletBinding()]param([System.Management.Automation.ActionPreference]$myverbose)
if ($myverbose) { $VerbosePreference = $myverbose }
Write-Verbose "message"
Then let's change the function:
function Import-ModuleVerbosely {
[CmdletBinding()]
param($Name, [Switch]$Force)
Import-Module $Name -Force:$Force -ArgumentList $VerbosePreference
}
Hey now we're getting somewhere! But.. it's kind of clunky isn't it?
You could go farther with it, making a full on proxy function for Import-Module, then making an alias to it called Import-Module to replace the real one.
Ultimately you're trying to do something not really supported, so it depends how far you want to go.

wlanapi.dll in powershell. Disable background scanning

Looking for some help with a script. I have tried and failed. I am not really advanced in powershell.
importing dlls is new for me. Any help is appreciated.
I want to use powershell to import the wlanapi.dll and use micrsoft native wifi functions to disable wireless background scanning, and enfore streaming mode.
The script should do this on execute. That way I can run it, or set it in a start up script.
https://learn.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlansetinterface?redirectedfrom=MSDN
Functions I am wanting to use:
wlan_intf_opcode_background_scan_enabled
wlan_intf_opcode_media_streaming_mode
Import is something you do via the PSModule paths where your modules or DLLs live.
You must tell PowerShell where the DLL is, no different than you'd have to if you loaded a module (.psm1 file with or without a manifest) of which you did not install to one of the defined PowerShell module paths.
You can use Add-Type...
Add-Type -Path $UncToCustomDll
... yet also, you can also use reflection:
$customDLL = 'UncToYourDLL'
See also Lee Holmes article on the topic here:
Load a Custom DLL from PowerShell
If you try to import and it's not in a know location, you get this.
Import-Module SomeNewCustomOr3rdP.dll
Import-Module : The specified module 'SomeNewCustomOr3rdP.dll' was not loaded because no valid module file was found in any module directory.
Of course, that error is pretty specific. It has no idea where to find it because that name does not match a module name.
So, this ...
Import-Module 'c:\users\mj\desktop\SomeNewCustomOr3rdP.dll'
Or create a folder of the same basename as the DLL in the PSModulePath, copy the DLL to the that named folder and use import as normal
C:\Users\<username>\Documents\WindowsPowerShell\Modules\SomeNewCustomOr3rdP\SomeNewCustomOr3rdP.dll'
Then this...
Import-Module SomeNewCustomOr3rdP
... should work as expected. All-in-all, Add-Type, Import-Module, and Reflection.Assembly::LoadFile($customDll), all accomplish the same thing, grant you access to the resource you specified.
If you are using a 3rdP DLL/Module, all this has to be done manually. If you are using published modules/packages that are in the MS powershellgallery.com, then this:
# Find all modules with wlan in the name
Find-Module -Name '*wlan*' |
Format-Table -AutoSize
# find all packages with wlan in the name
Find-Package -Name '*wlan*' |
Format-Table -AutoSize
# Get the detail on wlanapi specifically
Find-Package -Name 'wlanapi'
# Download and save a module or package
Find-Package -Name 'wlanapi' |
Save-Package -Path "$env:USERPROFILE\Documents\WindowsPowerShell\Modules"
Install-Package -Name 'wlanapi' -Force
Import-Module -Name wlanapi

Error while doing Get-Module in powershell 2.0

I'm new to powershell.I'm facing an error while working on an existing script.
In C:/Scripts I have 3 files . One .ps1 file and 2 .psm1 file.
ps1 file Name : RunScript.ps1
This imports .psm1 files like below.
$modules = #(
"$PsscriptRoot\Modules\Module1.psm1",
"$PsscriptRoot\Modules\Module2.psm1"
)
foreach ($module in $modules) {
if (-not $(Get-Module $module)) {
Import-Module $module -Force -WarningAction SilentlyContinue
}
}
This above code is giving me error as below
Get-Module : Running the Get-Module cmdlet without ListAvailable
parameter is not supported for module names that include a path. Name
parameter has this element 'C:\Scripts\Modules\Module1.psm1' which
resolves to a path. Update the Name parameter to not have path
elements, and then try again.
It is expecting to not give path of the file. But how do I do Get-Module in this scenario. Any help is very helpful.Thanks.
Get-Module returns modules imported into the current session or modules installed that are available for you to import. You should install your module then it will be available with the get-module cmdlet by using the name of your module.
I'm not quite sure what you're trying to do with your script? You only want to import it if its not already imported?
I would have added this as a comment but I cant yet...

Internal functions in PowerShell Module

I'm writing a module, and I have some helper functions that I don't want exposed, but I do want available to module functions internally. I have set up my directory structure like:
root\
..Private
....Invoke-PrivateTest.ps1
....private.psd1
....private.psm1
..Public
....Get-Something.ps1
....Public.psd1
....Public.psm1
..test.psd1
I've setup a repository on github https://github.com/jpbruckler/test that has all the module files in it.
The behavior that I'm expecting is that Get-Something is a public function. When running Get-Command -Module Test it should be listed. On the contrary, Invoke-PrivateTest should not be in the output of that command.
When calling Get-Something it should output the text Invoke-PrivateTest called. Instead, I get an error stating that the command Invoke-PrivateTest doesn't exist.
I am explicitly saying in test.psd1 that only the Get-Something function is to be exported.
Both the Private module and the public module are being called via the NestedModules property in test.psd1. Any help or pointers would be appreciated.
Unless you have other reasons to put the code into separate (sub)modules I'd keep it in one folder and control what's exported via the function names. Use "official" notation (<Verb>-<Noun>) for the names of public functions and omit the hyphen in the names of private function (<Verb><Noun>). That way you can export public functions in your global .psd1 like this:
FunctionsToExport = '*-*'
#PetSerAl pointed me in the right direction. Ultimately this came down to a scoping issue. The way I had the module arranged, each sub-module would need to make a call to load the private module, which is a bunch of code duplication - and also what I was hoping to avoid by splitting out some helper functions.
To get it all to work, instead of multiple sub modules, I just broke up the Public folder into sub folders that will hold scripts that do similar things, basically removing all the .psd1 and .psm1 files from the Public directory. I did the same thing for the Private directory. This left me with a bunch of loose .ps1 files that I load in test.psm1 with the following code:
$Private = (Get-ChildItem -Path (Join-Path $PSScriptRoot 'Private') -Filter *.ps1)
$Public = (Get-ChildItem -Path (Join-Path $PSScriptRoot 'Public') -Filter *.ps1 -Recurse)
foreach ($Script in $Public) {
. $Script.FullName
Export-ModuleMember $Script.BaseName
}
foreach ($Script in $Private) {
. $Script.FullName
}
I've modified the test module at https://github.com/jpbruckler/test to reflect the changes I made.

Get the path of the importing script from within a module?

Is there a way to get the path of a script that imported a module from within that module?
The script module I'm writing is meant to load settings from files relative to the importing script. I plan on reusing the module for a number of projects, so I would prefer if the module could make no assumptions about where its being imported from.
This is a nice to have, it would be great the module could be as implicit as possible. If all else fails though, I can just have the caller pass in its location.
Unfortunately everything I've attempted so far returns the path to the module (not what imported it). Here's a simple demonstration:
Test-RelativeModule.ps1, Stored at: c:\test\
import-module "$PSScriptRoot\mod\Test.psm1"
Test.psm1, Stored at: c:\test\mod\
# returns 'c:\test\mod'
write-host "`$PSScriptRoot: $PSScriptRoot"
# returns 'c:\test\mod'
# The value of $MyInvocation.MyCommand.Path is 'c:\test\mod\Test.psm1'
write-host "Split Invoation: $(Split-Path $MyInvocation.MyCommand.Path)"
# returns whatever path the console is currently residing
write-host "Resolve Path: $((resolve-path '.\').path)"
# what I'm looking for is something to return 'c:\test' from within the module
# without making any assumptions about the folder structure
Try this:
Write-Host "My invoker's PSScriptRoot: $($MyInvocation.PSScriptRoot)"