What is the purpose of the *.psm1 files in a Powershell module? - powershell

So I implemented my first Powershell module with a bunch of .ps1 files (one per function) and the .psd1 manifest file.
I am trying to understand what is the purpose of the .psm1 files - do I need them at all in my module?
What is their added value?
EDIT 1
Here is my .psd1 file:
#{
ModuleVersion = "0.0.19106.59054"
GUID = "59bc8fa6-b480-4226-9bcc-ec243102f3cc"
Author = "..."
CompanyName = "..."
Copyright = "..."
Description = "..."
ScriptsToProcess = "vsts\config.ps1"
VariablesToExport = #(
"TfsInstanceUrl",
"TfsApiVersion",
"QANuGetRepoUrl"
)
NestedModules = #(
"db\Backup-Database.ps1",
...
"vsts\work\Get-WorkItems.ps1"
)
FunctionsToExport = #(
"Assert-ExtractionDestFolder",
...
"Write-HostIfNotVerbose"
)
PrivateData = #{
PSData = #{
ExternalModuleDependencies = "SqlServer"
}
}
}
Like I said, each function is in its own file.

what is the purpose of the .psm1 files - do I need them at all in my module?
In script modules, i.e., modules authored in PowerShell (as opposed to compiled binary cmdlets), it is only *.psm1 files that provide the module-specific behaviors distinct from regular *.ps1 script files (separate, isolated scope, private commands, control over exported commands).
Typically, a script module manifest has a RootModule entry pointing to (the main) *.psm1 file; for smaller modules it is not uncommon for all of the module's functionality to be implemented in this one *.psm1 file.
In fact, a stand-alone *.psm1 file can also act as a module, though it doesn't integrate with PowerShell's module auto-discovery and auto-loading feature.
Note that if you were to use a regular *.ps1 script directly in RootModule, its definitions would be loaded into the caller's scope, not the module's; that is, you would lose the benefits of a module.
Even though you're listing regular *.ps1 scripts in your NestedModules manifest entry, by virtue of using that specific entry these scripts are dot-sourced in the module's context and thereby become part of the module:
This is conceptually equivalent to creating and referencing a root *.psm1 script in RootModule, and - instead of defining a NestedModules entry - explicitly dot-sourcing your *.ps1 scripts from there - see bottom section.
Note that if you were to reference *.psm1 files in NestedModules, they would truly become nested modules, with their own scopes; a nested module is usable from the enclosing module, but not visible to the outside world (though you can list it among the loaded modules with Get-Module -All).
Listing *.ps1 files in NesteModules vs. dot-sourcing them from the RootModule
While there should be no difference in functionality, using a *.psm1 RootModule to dot-source the *.ps1 files containing your module's functions can potentially simplify things, if you simply need to dot-source all *.ps1 files located in your module directory's subtree:
# Add this to the *.psm1 file specified in 'RootModule'
# Find all *.ps1 files in the module's subtree and dot-source them
foreach ($script in
(Get-ChildItem -File -Recurse -LiteralPath $PSScriptRoot -Filter *.ps1)
) {
. $script
}
If you need to load the scripts in a specific order, need only a subset of the scripts, or want to speed things up slightly (though I doubt that the speed difference will be noticeable), you can dot-source the files individually from a RootModule *.psm1 file, as an alternative to listing them in the NestedModules entry:
# Add this to the *.psm1 file specified in 'RootModule'
# Dot-source the *.ps1 files individually.
. "$PSScriptRoot/db/Backup-Database.ps1"
# ...
. "$PSScriptRoot/vsts/work/Get-WorkItems.ps1"
Again, all of the above approaches are functionally equivalent.
Given that you explicitly export functions via the ExportedFunctions entry (as is advisable), the use of *.ps1 files that must be dot-sourced is ultimately an implementation detail that is not relevant for the purposes of command discovery and module auto-loading - it only matters at actual import time.

.psm1 file is powershellmodule file. When we create script module we write all the functions of a module in a .psm1 file then we export the functions and then we can use those functions by importing module.
.psm1 is basically refers the powershellmodule. Powershell directly identifies anything which is written in this file will be a part of module.

Related

Making a PowerShell script portable with its dependencies

I am working on a PowerShell script that requires a specific PowerShell module to be installed on the machine where the script is run. This module provides additional functionality that is crucial for the script to work correctly.
I would like to make my script portable, so that I can put my script and the module in a folder and copy it to another machine and run it directly without the need for manual installations. Is this possible? I have tried searching for a solution online, but I couldn't find anything that specifically addresses my problem.
Preparations
Copy the module directory (using the same directory structure as the installed module) into a sub directory where your script is located.
For example, the directory structure could look like this for the Pester module:
Script Directory
YourScript.ps1
Modules
Pester
5.4.0
pester.psm1
pester.psd1
…
When the module is available in PSGallery you can alternatively save the module without installing it first. For example to save the Pester module from PSGallery in a directory structure like above:
md Modules
Save-Module -Name Pester -Path Modules
Loading the portable module
You basically have two ways to load the module.
Explicitly using Import-Module, specifying the full path of the module directory, without the version sub directory.
Implicitly by inserting the "Modules" path into the $env:PSModulePath variable. This enables simple import using just the module name and module auto-loading (as if the module were actually installed). This might be preferred if your script is split into multiple files. In this case you only have to modify the root script and any scripts loaded by the root script will automatically use the portable module(s).
Example for using Import-Module:
# Import module from a sub directory relative to the current script
Import-Module $PSScriptRoot\Modules\Pester
# Now using functions from portable Pester module
Describe 'Portable Module Test' {
It 'Should have loaded portable module' {
$portableModuleFilePath = (Get-Item $PSScriptRoot\Modules\Pester\*\Pester.psm1).FullName
(Get-Module Pester).Path | Should -Be $portableModuleFilePath
}
}
Example for using $env:PSModulePath:
# Insert portable module directory at the front of PSModulePath so our portable
# version of the module will be preferred over any installed version.
# This is only in script scope, the system environment variable won't be modified!
$env:PSModulePath = "$PSScriptRoot\modules;$env:PSModulePath"
# Now using functions from portable Pester module, which PowerShell loads automatically.
Describe 'Portable Module Test' {
It 'Should have loaded portable module' {
$portableModuleFilePath = (Get-Item $PSScriptRoot\Modules\Pester\*\Pester.psm1).FullName
(Get-Module Pester).Path | Should -Be $portableModuleFilePath
}
}

How to export variables from a PowerShell module

I've defined a variable in a psm1 file but when I try to access it in another script, after importing the module, I'm not seeing the value set in the psm1 file.
globals.psm1
$blah = "hello world"
my-script.ps1
Import-Module "$PSScriptRoot\globals.psm1" -Force -Verbose
Write-Output "blah: ${blah}"
output
PS C:\blah> .\my-script.ps1
VERBOSE: Loading module from path 'C:\blah\globals.psm1'.
blah: ''
I thought all variables get exported by default. I must be interrupting this wrong:
Specifies the variables that the module exports to the caller's session state. Wildcard characters are permitted. By default, all variables ('*') are exported
source: MSFT Docs -> How to write a PowerShell module manifest
(CTRL + F on 'VariablesToExport' to find the quoted text)
And yes, if I export the variable, I can access it but the documentation says: 'By default, all varialbes ('*') are exported so what am I doing wrong or misunderstanding? 🤔
globals.psm1
$blah = "hello world"
Export-ModuleMember -Variable blah
Your module is not using a module manifest (a companion .psd1 file whose RootModule entry points to your .psm1 file in the case of script modules), whereas the documentation you quote pertains to module manifest-based modules.
If a module consists only of a .psm1 file, and that file contains no Export-ModuleMember calls, the following rule applies:
Only functions and aliases are automatically exported.
Conversely, this means: in order to also export variables, you must use an Export-ModuleMember call - and if you do, the slate is wiped clean, so to speak, and you must explicitly specify all definitions you want to export (in the simplest case, use Export-ModuleMember -Function * -Alias * -Variable *).
Also, be sure to place this call at the end of your .psm1 file, to ensure that all definitions to export have already been defined.
Caveat, if a manifest (.psd1) is used:
The manifest's *ToExport keys apply on top of what the .psm1 file - implicitly or explicitly - exports, i.e. you can use it to further narrow what is to be exported, by explicitly enumerating the elements to export, which not only makes the module more self-describing, but also helps performance when PowerShell auto-discovers the commands in available, but not-(yet)-imported modules.
Therefore, if a manifest-based module wants to export variables, it too must have an explicit Export-ModuleMember call in its .psm1 file, with the manifest potentially narrowing down what variables are ultimately to be exported.
Generally, exporting variables from modules is best avoided, because:
it increases the risk of name collisions with variables of the same name defined elsewhere.
discovering which variables are exported by what module isn't as well-known as use of Get-Command is in order to determine what module a given function, cmdlet, or alias comes from. This is because (a) modules that export variables are rare and users generally don't expect it, and (b) the Get-Variable cmdlet - which can tell you what module a variable is defined in - isn't often used in practice.
To see which definitions a given module exports, pass -Verbose to the Import-Module call that imports it. Additionally, pass -Force in order to force re-loading of an already imported module.

Binary Powershell Cmdlet not exported from module

I created a Powershell module containing one Cmdlet implemented by C# and some advanced functions provided by a psm1 file. When loading the module, only the functions implemented in advanced functions are exported.
I registered the assembly to load as part of my module and exported the functions:
RequiredAssemblies = #("lib\provider.dll","lib\myCmdlet.dll")
FunctionsToExport = #('New-assemblyFunction','New-advancedFunction')
Also I tried to mix the above functionstoexport with the cmdlettoexport for the assembly provided Cmdlet. All kind of combinations did not show any success:
CmdletsToExport = #('New-assemblyFunction')
If I start the import-module in verbose mode, I can see that the assemblies are being loaded but only functions implemented in advanced functions are being exported. The New-assemblyFunction does not appear anywhere in the verbose report.
I can load the DLL manually (import-module) and the cmdlet is available.
Any clue what's wrong here or how to further analyse? I deblock-file'd all of them.
That's it:
Value of RequiredAssemblieskey not considered as PowerShell modules. You need to use RootModule (ModuleToProcess) or NestedModules key. – PetSerAl

How to load my custom PowerShell module? [duplicate]

I have to create a PowerShell script which does exactly same thing as my previous script, but this time I have to read a CSV file instead of an XML file. My plan is to create a PowerShell script which has common functions required for both scripts and re-use this common script file in both main files.
Suppose I create 2 main files in 2 directories in C:\ drive and keep my common file and other 3rd party libraries in a folder of D:\ drive, e.g. C:\script_1_folder\Script1.ps1, C:\script_2_folder\Script2.ps1 and common file and 3rd party libraries will be in D:\script_common.
How do I call\re-use common file in my main files (how to get the path, do I have to create an instance of common file and how do I use it)
What is the difference between
$script_path = $myinvocation.invocationname;
$script_folder = split-path $script_path -parent;
write-host $script_folder
$script_name = split-path $script_path -leaf;
$current_folder = [system.io.directory]::getcurrentdirectory()
[system.io.directory]::setcurrentdirectory($script_folder)
Set-Location $script_folder
add-type -path ".\AlexFTPS-1.1.0\AlexPilotti.FTPS.Client.dll"
and
$path = (split-path $MyInvocation.MyCommand.Path)
$loggerPath = $path + "\Logger\release\Logger.ps1";
.$loggerPath;
$logger = Logger;
$logger.load($path + "\Logger\config\log4ps.xml","log.log");
and what is the best way to do it with regard to my problem?
How do I create a temp folder in windows temp folder?
Common Code In Powershell
You can just put the code you want to include in a different PS1 file, and then "dot source" that file to include it in the current scope:
. D:\script_common\MyCode.ps1
That's all there is to that.
Using a Module
You might consider using a module instead, which can be included using the Import-Module cmdlet. You might have used this to work with things like Active Directory, where you could do something like this:
Import-Module ActiveDirectory
In that case, you only need the name of the module because it's in a special directory.
To write your own modules in Powershell, you name the module with a .psm1 extension. Typically, you don't do free floating code in one of these; you write functions which are then available to the code which imports the module.
To import a script module from anywhere, use the full path:
Import-Module D:\script_common\MyModule.psm1
Module Paths
When you create your own modules, you can keep them any old place and then refer to them by their full path (as above). There are also several locations that Powershell looks for modules:
$PSHome\Modules (%Windir%\System32\WindowsPowerShell\v1.0\Modules) -- Reserved for modules that ship with Windows. Do not put things here.
$Home\Documents\WindowsPowerShell\Modules (%UserProfile%\Documents\WindowsPowerShell\Modules)
%ProgramFiles%\WindowsPowerShell\Modules -- this isn't mentioned in the link, and seems to be used more for Desired State Configuration modules (probably because it applies to the entire system).
These are defaults, but Powershell uses its own environment variable called PSModulePath to determine where to look, and much like PATH you can add your own folder(s) to that variable.
That lets you keep your modules in your own location. Do see the link for more info on how to structure your folders and how to do naming.
So as far as keeping your modules and "3rd party" modules in the same place, that depends on the 3rd party stuff. It may install its own modules in its own place and modify the path, or it may just let you put them wherever you want.
Creating a Temp Folder
You can use the TEMP or TMP environment variables to get the path of the temp folder. To retrieve them in Powershell, use $env:TEMP or $env:TMP.
You'll have to come up with a unique name of a folder to create in there. One way to do that might be to use a GUID:
$dirName = [System.Guid]::NewGuid().ToString()
New-Item -Path "$($env:TEMP)\$dirName"
You should be able to dot source the script like that:
. "C:\script_common\script.ps1"
after that you can use all the functions like they were in the script you are running.
But... the better way to do it would be to create a module with your common functions (How to here: Scripting Guy´s Blog. (TLDR Version: place functions into psm1 file, put into modulepath, import using Import-Module, profit)
For creating a folder:
New-Item C:\Temp\yourfolder -type directory
Here is my attempt to create a template system in powershell : https://github.com/kayasax/PowershellTemplate
It allows to reuse functions you save in the repository by using tags in the template
eg :
<include logging/log>
The content of the file log.ps1 found in the logging directory of the function repository will be inserted when tranforming template to script

How can I re-use/import script code in PowerShell scripts?

I have to create a PowerShell script which does exactly same thing as my previous script, but this time I have to read a CSV file instead of an XML file. My plan is to create a PowerShell script which has common functions required for both scripts and re-use this common script file in both main files.
Suppose I create 2 main files in 2 directories in C:\ drive and keep my common file and other 3rd party libraries in a folder of D:\ drive, e.g. C:\script_1_folder\Script1.ps1, C:\script_2_folder\Script2.ps1 and common file and 3rd party libraries will be in D:\script_common.
How do I call\re-use common file in my main files (how to get the path, do I have to create an instance of common file and how do I use it)
What is the difference between
$script_path = $myinvocation.invocationname;
$script_folder = split-path $script_path -parent;
write-host $script_folder
$script_name = split-path $script_path -leaf;
$current_folder = [system.io.directory]::getcurrentdirectory()
[system.io.directory]::setcurrentdirectory($script_folder)
Set-Location $script_folder
add-type -path ".\AlexFTPS-1.1.0\AlexPilotti.FTPS.Client.dll"
and
$path = (split-path $MyInvocation.MyCommand.Path)
$loggerPath = $path + "\Logger\release\Logger.ps1";
.$loggerPath;
$logger = Logger;
$logger.load($path + "\Logger\config\log4ps.xml","log.log");
and what is the best way to do it with regard to my problem?
How do I create a temp folder in windows temp folder?
Common Code In Powershell
You can just put the code you want to include in a different PS1 file, and then "dot source" that file to include it in the current scope:
. D:\script_common\MyCode.ps1
That's all there is to that.
Using a Module
You might consider using a module instead, which can be included using the Import-Module cmdlet. You might have used this to work with things like Active Directory, where you could do something like this:
Import-Module ActiveDirectory
In that case, you only need the name of the module because it's in a special directory.
To write your own modules in Powershell, you name the module with a .psm1 extension. Typically, you don't do free floating code in one of these; you write functions which are then available to the code which imports the module.
To import a script module from anywhere, use the full path:
Import-Module D:\script_common\MyModule.psm1
Module Paths
When you create your own modules, you can keep them any old place and then refer to them by their full path (as above). There are also several locations that Powershell looks for modules:
$PSHome\Modules (%Windir%\System32\WindowsPowerShell\v1.0\Modules) -- Reserved for modules that ship with Windows. Do not put things here.
$Home\Documents\WindowsPowerShell\Modules (%UserProfile%\Documents\WindowsPowerShell\Modules)
%ProgramFiles%\WindowsPowerShell\Modules -- this isn't mentioned in the link, and seems to be used more for Desired State Configuration modules (probably because it applies to the entire system).
These are defaults, but Powershell uses its own environment variable called PSModulePath to determine where to look, and much like PATH you can add your own folder(s) to that variable.
That lets you keep your modules in your own location. Do see the link for more info on how to structure your folders and how to do naming.
So as far as keeping your modules and "3rd party" modules in the same place, that depends on the 3rd party stuff. It may install its own modules in its own place and modify the path, or it may just let you put them wherever you want.
Creating a Temp Folder
You can use the TEMP or TMP environment variables to get the path of the temp folder. To retrieve them in Powershell, use $env:TEMP or $env:TMP.
You'll have to come up with a unique name of a folder to create in there. One way to do that might be to use a GUID:
$dirName = [System.Guid]::NewGuid().ToString()
New-Item -Path "$($env:TEMP)\$dirName"
You should be able to dot source the script like that:
. "C:\script_common\script.ps1"
after that you can use all the functions like they were in the script you are running.
But... the better way to do it would be to create a module with your common functions (How to here: Scripting Guy´s Blog. (TLDR Version: place functions into psm1 file, put into modulepath, import using Import-Module, profit)
For creating a folder:
New-Item C:\Temp\yourfolder -type directory
Here is my attempt to create a template system in powershell : https://github.com/kayasax/PowershellTemplate
It allows to reuse functions you save in the repository by using tags in the template
eg :
<include logging/log>
The content of the file log.ps1 found in the logging directory of the function repository will be inserted when tranforming template to script