PowerShell module - separate file for each cmdlet - powershell

I've been strugling with this for a while now. I intend to create new PowerShell module for my project. Aim is to package several custom cmdlets into standalone unit, which I could deploy to other machines via our Nexus repository (or via anything else).
Problem: Everywhere I look, I see tutorials packaging all PowerShell functions/cmdlets into single *.psm1 file. File is stored inside equally named directory, which actually represents module itself.
Question: Is there a way, how to separate each cmdlet/function into standalone file? If I have a module consisting of several cmdlets, it's not very convenient to put them all in single *.psm1 file.
Thanks
Matthew

You could also use a manifest file. "A module manifest is a .psd1 file that contains a hash table. The keys and values in the hash table do the following things:
Describe the contents and attributes of the module.
Define the prerequisites
Determine how the components are processed.
Manifests are not required for a module. Modules can reference script files (.ps1), script module files (.psm1), manifest files (.psd1), formatting and type files (.ps1xml), cmdlet and provider assemblies (.dll), resource files, Help files, localization files, or any other type of file or resource that is bundled as part of the module. For an internationalized script, the module folder also contains a set of message catalog files. If you add a manifest file to the module folder, you can reference the multiple files as a single unit by referencing the manifest." (Source)
So you can use ps1 files instead of psm1 files directly from psd1 files:
# Modules to import as nested modules of the module specified in RootModule/ModuleToProcess
NestedModules = 'Get-WUList.ps1','Add-WUOfflineSync.ps1'
# Functions to export from this module
FunctionsToExport = 'Get-WUList','Add-WUOfflineSync'

Following up on #MatthewLowe - I've made my .psm1 a "one liner" as follows; this seems to work, provided that none of the scriptlets depend on one whose name is alphabetically after itself:
Get-ChildItem -Path $psScriptRoot\*.ps1 | ForEach-Object { . $_.fullname; Export-ModuleMember -Function ([IO.PATH]::GetFileNameWithoutExtension($_.fullname)) }

Just posting this answer which I found as I was actually wrinting question itself :-). I downloaded few PowerShell modules from internet and looked inside, I found answer there. But since I got stuck on this for few hours (new to powershell ;-)), I decide to post this anyway, for future generations :-P.
You can put your cmdlets (*.ps1 files) EACH into separate file. Store them inside your module directory and create *.psm1 file. Then, dot-source your *.ps1 cmdlets/functions into this *.psm1.
However, reference to current module directory where your *.ps1 files are stored must be provided like this
". $psScriptRoot/moduleFunc1.ps1" AND NOT LIKE ". ./moduleFunc1.ps1"
Enjoy
Matthew

Related

How to require a dll inside .psd1 for a .psm1 that is being invoked from a .ps1?

I read a lot of the answers here and I can't seem to find what I am looking for. So please bear with me.
Each psm1 is a class.
I have:
Main.ps1
Modules/module01.psm1
Modules/module02.psm1
DLL/dllInQuestion.dll
I am trying to load a dll to use inside module02.psm1. I know that in order to do so I have to require it inside a .psd1. I created a psd1 for module02 and put it in the same folder and imported it like this "Import-Module" before the code but it didn't work.
I also tried to create a psd1 for Main.ps1 but it didn't work.
Can I require (using using module statements and Add-Type) all modules and dll inside a .ps1 script and require it inside a .psd1 for Main.ps1?
Thank you.

PowerShell Module Manifest FileList property - specifically what does it do?

So I am trying to refine my PowerShell module skills.
There is a parameter in the module manifest (.psd1) called 'FileList'.
It has the helpful documentation
# List of all files packaged with this module
FileList = #()
So, I have a module that contains a few .psm1 files. Perfect! (I thought). Perhaps I should list these files there?
However, the files I do list in FileList seem to get resolved to full path names when the module is imported, but none of the functions they contain are available?
Does this mean I need to list the .psm1 files in two places?
I had been listing them in NestedModules, which did import the functions, but I'm unsure if this is right?
Does anyone have any insight on specifically what FileList does and does not do, and how it should be used with a PowerShell module?
This parameter isn't used at the moment by PowerShell.
From the documentation:
List of all files packaged with this module. As with ModuleList, FileList is to assist you as an inventory list, and is not otherwise processed.
You can give a full list of files included. But you still have to use the NestedModules parameter (depending on the structure of your module), because it has a different purpose.

Best practice for a module with several files

I've gathered and created quite a few Powershell functions that I use daily on my job. To make things easier to maintain and organized, I've created modules. Each module has its own folder with several files in it. Each file has one or more functions.
In the past, I've organized things as such:
\Modules\
\SystemTools\
Hotfixes.psm1
Services.psm1
SystemTools.psd1
\NetworkTools\
ActiveDirectory.psm1
Connections.psm1
NetworkTools.psd1
with Export-ModuleMember -Function in each psm1 file, an empty RootModule line in the manifest (psd1) file and all my psm1 files as an array on the NestedModules line in the manifest file.
But I'm not sure that's how it was intended to be used, and if it follows best practices regarding modules with several files.
So I've recently changed my Modules folder as such:
\Modules\
\SystemTools\
Hotfixes.ps1
Services.ps1
SystemTools.psd1
SystemTools.psm1
\NetworkTools\
ActiveDirectory.ps1
Connections.ps1
NetworkTools.psd1
NetworkTools.psm1
So I've
renamed all psm1 files to ps1
added a psm1 file where I dot source all ps1 files in the same folder
set the psm1 file as RootModule and no NestedModules
Questions
Both seem to work, but which one is better ?
If a function defined in the SystemTools module needs to use a function defined in the NetworkTools module, should I use Import-Module ? Isn't there a risk of circular dependency ?

Powershell dot sourcing opens up file in notepad

Everytime i dot source a file in PowerShell it opens a copy of the file in notepad.
Exe:
.\MyScript.ps1
The script runs fine - its just really annoying having these pop up all the time. Is there a way to suppress this?
I'm on windows 7 x64 and using the latest version of PowerShell.
Ex2: This is still launching notepad.
cls
Set-Location "\\PSCWEBP00129\uploadedFiles\psDashboard\"
. .\assets\DCMPull\Powershell\SqlServerTransfer.psm1
. .\assets\DCMPull\Powershell\RunLogging.psm1
You cannot dot source PowerShell files with the .psm1 file extension. One option is to rename them to .ps1.
Alternatively (and, in my opinion the better approach), you can load the PowerShell modules using Import-Module <module.psm1>. Just note that the behavior of Import-Module is different from dot sourcing it. Dot sourcing runs the script in the current scope and also persists all variables, functions, etc.in the current scope. Import-Module does not do that.
Although not very common, you can also export variables from modules with Export-ModuleMember.
Adding to Raziel's answer, there's a lot of thought that went into only being able to dot source files with .ps1 extension, and otherwise why it tries to run it as a system executable. Here's a snippet from PeterWhittaker on GitHub:
. ./afile would only execute something if there's either an
extension-less but executable aFile in the current dir, or a
(not-required-to-be-executable) afile.ps1 file, with the former taking
precedence if both are present; if the file exists, but is neither
executable nor has extension .ps1, it is opened as if it were a
document.
. <filename> with <filename> being a mere name (no path component) by
(security-minded) design only ever looks for a file of that name in
the directories listed in $env:PATH (see below), not in the current
directory.
I encountered exactly the same situation : If the point source imports the .psm1 file, the file will be opened directly instead of importing the code in the file.
Because the function of point source import is only valid in the file with suffix of.ps1, if the suffix does not meet the requirements, it will not be regarded as path, but as a code , so it is like running the corresponding string directly, and the effect is naturally to open the file.
So,this phenomenon is not aimed at .PSM1,if you change the extension to TXT, it will have the same effect. It will have the same effect for any file whose suffix is not .PS1.
You can bypass this problem by creating symbolic links or hard links!
In PowerShell 7, it's easy to create links using New-Item.

Is it possible to set Powershell module name/version without using a manifest?

I can see that without a manifest the default module name is set to the .psm1 filename.
But is there a way to set it within the module's code?
Also I would like to set the version in code.
If you are importing a binary module then the module version is pulled from the assembly metadata. However the name is simply the DLL's filename. For a script module (.psm1), you can't provide this info to PowerShell other than via a module manifest as Ansgar has already pointed out.
BTW you can get at the Name and Version properties of the module from within the module ($MyInvocation.MyCommand.ScriptBlock.Module.Name) but those properties are read-only.