I have a module (MyModule) under a non-standard path, i.e. not under the usual locations listed in $env:PSModulePath -split ";". I have, however, added the "production" path to MyModule to that environment variable while I continue to work on a "development" copy.
Whilst trying to debug something, I loaded the module (with $VerbosePreference = "Continue") using the following command and immediately saw two seemingly contradictory lines of Verbose output:
[D:\Dev\UserA\]> Import-Module D:\Dev\UserA\libs\PowerShell\MyModule
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\MyModule.psd1'.
VERBOSE: Loading module from path 'D:\Dev\usera\MyModule2\MyModule.psm1'.
I would like to understand why Import-Module appears to be loading the module twice, especially as the second path is incorrect.
MORE DETAIL:
The folder structure for the module is:
MyModule\MyModule.psd1
MyModule\MyModule.Test-Module.xml
MyModule\MyModule1\MyModule.psm1
MyModule\MyModule2\MyModule.psm1
Note (1) I retained an older "version 1" of this module in a MyModule1 sub-folder and put my updated "version 2" file in a MyModule2 sub-folder and (2) that the .xml file is used by a custom module-testing script to list test cases. I'm pretty sure that the latter can be ignored.
My module manifest (.psd1) file contains the following, with all other lines being blanks or comments:
#{
RootModule = '.\MyModule2\MyModule.psm1'
ModuleVersion = '2.0.0.0'
GUID = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
Author = 'Old Developer (v1.x) & New Developer (v2.x)'
CompanyName = 'MyCompany'
Copyright = '(c) 2013-2015 MyCompany. All rights reserved.'
Description = 'Really useful functions'
FileList = #(
'.\MyModule.psd1'
'.\MyModule.Test-Module.xml'
'.\MyModule1\MyModule.psm1'
'.\MyModule2\MyModule.psm1'
'.\MyModule2\Examples\Archive-FilesWithCompression.ps1'
)
}
Clearly I have used relative paths for the files, esp. the RootModule key; this is necessary as I cannot be sure where the module will be copied when I share it.
Going back to the Verbose output, I can see that the two lines show (1) the correct path to the PSD1 file and (2) an invalid path to the PSM1 file. I did notice that the second path has the username in lower-case, which is how I happened to type it when I Set-Location before testing. So, it looks like the first path is taken by appending MyModule.psd1 to the path given to the Import-Module cmdlet and the second is a concatenation of (Get-Location) and the RootModule path.
This only seems to happen to this module. I have others under the same 'root' folder which don't exhibit this behaviour.
Um. OK. I may have worked it out, at least in part...
I had accidentally made a copy of the MyModule2 sub-folder under the location from where I was running the Import-Module cmdlet. Once I removed that folder the verbose output started to make more sense:
[D:\Dev\UserA\]> Import-Module D:\Dev\UserA\libs\PowerShell\MyModule
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\MyModule.psd1'.
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\.\MyModule2\MyModule.psm1'.
VERBOSE: Exporting function '<function>'.
....
I guess this means that when PowerShell resolves the RootModule in the manifest file it looks under the current path first and then the main module folder if nothing is found. I find this counter-intuitive as I'd expect that any relative paths in the manifest would always be relative to the PSD1 file, not the current location.
If I then try to import the module again immediately I get this:
[D:\Dev\UserA\]> Import-Module D:\Dev\UserA\libs\PowerShell\MyModule
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\MyModule.psd1'.
VERBOSE: Importing function '<function>'.
....
That is, only one "loading module" line, rather than the two lines of verbose output. I then tried the -Force switch and got the following:
[D:\Dev\UserA\]> Import-Module D:\Dev\UserA\libs\PowerShell\MyModule -Force
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\MyModule.psd1'.
VERBOSE: Removing the imported "<function>" function.
....
VERBOSE: Loading module from path 'D:\Dev\UserA\libs\PowerShell\MyModule\.\MyModule2\MyModule.psm1'.
VERBOSE: Exporting function '<function>'.
....
VERBOSE: Importing function '<function>'.
....
So, it seems that the two lines of verbose output appear when the module is first imported, or forcibly re-imported, but if it is already part of the session then you only see the first line.
Also, I finally spotted that the verbose output differentiated between exporting functions and importing them. The functions are exported in the order that they are defined in the PSM1 file, but are imported in alphabetical order. This suggests a one- or two-stage process is used depending on whether the function definitions are being re-read from the PSM1 file, tying in with whether we see one or two lines of verbose output.
Or, to answer the question more directly, it appears that there are two lines saying "Loading module..." when it needs to read the .psm1 file, and only one line when PowerShell is already aware of the module and its contents.
Related
I would like To store a variable in a config file for a .ps1(powershell script) converted to .exe using ps2exe
$LnkPath="...\" #Directory Path of .lnk
$LnkFile="\nnnn.lnk" #name of .lnk file
Invoke-Item $ScriptPath + $LnkFile
I was hoping to have $LnkFile and $LnkPath as config file variables so if the version of the lnk stops working, i can just point to a new lnk.
There is a reason why the version of the .lnk file stops working, but it is complicated, and not worth anyone's time.
edit:
The config file created with the optional -configFile switch isn't meant for use by the wrapped script - it merely contains runtime metadata for the generated executable (in the form of an XML file placed alongside the executable with additional extension .config).
However, you can create your own config file.
While PowerShell has a configuration-data format that uses hashtable-literal syntax, which can be read with Import-PowerShellDataFile, as of PowerShell 7.2.x there is no way to create this format programmatically.
A simple alternative that supports both reading and programmatic creation is to use a JSON file:
The following assumes that your script file is foo.ps1, to be converted to foo.exe, with a configuration file foo.json located in the same directory (which you'll have to bundle with your .exe file when deploying it):
First, create your JSON config file:
#{ LnkPath = '...\'; LnkFile = 'nnnn.lnk' } | ConvertTo-Json > foo.json
Now you can read this file from foo.ps1 / foo.exe as follows:
# Determine this script's / executable's full path.
$scriptOrExePath =
if ($PSCommandPath) { # Running as .ps1
$PSCommandPath
} else { # Running as .exe"
Convert-Path ([Environment]::GetCommandLineArgs()[0])
}
# Look for the JSON config file in the same directory as this script / executable, load it and parse it into an object.
$config =
Get-Content -Raw ([IO.Path]::ChangeExtension($scriptOrExePath, '.json')) |
ConvertFrom-Json
# $config is now an object with .LnkPath and .LnkFile properties.
$config # Output for diagnostic purposes.
Note the need to use [Environment]::GetCommandLineArgs() to determine the executable path when running as an .exe file, because the usual automatic variables indicating the script path ($PSCommandPath) and script directory ($PSScriptRoot) aren't available then.
I have a powershell script that generates a report, and I have connected it to an io.filesystemwatcher. I am trying to improve the error handling capability. I already have the report generation function (which only takes in a filepath) within a try-catch loop that basically kills word, excel and powerpoint and tries again if it fails. This seems to work well but I want to embed in that another try-catch loop that will restart the computer and generate the report after reboot if it fails a second consecutive time.
I decided to try and modify the registry after reading this article: https://cmatskas.com/configure-a-runonce-task-on-windows/
my plan would be, within the second try-catch loop I will create a textfile called RecoveredPath.txt with the file path being its only contents, and then add something like:
Set-ItemProperty "HKLMU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -Name '!RecoverReport' -Value "C:\...EmergencyRecovery.bat"
Before rebooting. Within the batch file I have:
set /p RecoveredDir=<RecoveredPath.txt
powershell.exe -File C:\...Report.ps1 %RecoveredDir%
When I try to run the batch script, it doesn't yield any errors but doesn't seem to do anything. I tried adding in an echo statement and it is storing the value of the text file as a variable but doesn't seem to be passing it to powershell correctly. I also tried adding -Path %RecoveredDir% but that yielded an error (the param in report.ps1 is named $Path).
What am I doing incorrectly?
One potential problem is that not enclosing %RecoveredDir% in "..." would break with paths containing spaces and other special chars.
However, the bigger problem is that using mere file name RecoveredPath.txt means that the file is looked for in whatever the current directory happens to be.
In a comment your state that both the batch file and input file RecoveredPath.txt are located in your desktop folder.
However, it is not the batch file's location that matters, it's the process' current directory - and that is most likely not your desktop when your batch file auto-runs on startup.
Given that the batch file and the input file are in the same folder and that you can refer to a batch file's full folder path with %~dp0 (which includes a trailing \), modify your batch file to look as follows:
set /p RecoveredDir=<"%~dp0RecoveredPath.txt"
powershell.exe -File C:\...Report.ps1 "%RecoveredDir%"
I just got an error in Powershell:
because the module nesting limit has been exceeded. Modules can only be nested to 10 levels.
I found this and found something called a "module manifest". I already have a module .psm1 files - why do I also need this?
Note: I dont have 10 levels of modules, I have 10 modules, all loaded in by one import.psm1 file.
The module nesting level getting exceeded is typically the result of accidentally getting into an infinite recursion during module import (irrespective of whether you import via Import-Module or the PSv5+ using module statement).
This can happen whether or not your module has a manifest; the answer to the linked question shows how it can happen with a manifest; here's an example without one: the following foo.psm1 module causes an infinite recursion that results in the error message you saw
# Create sample module (without manifest).
#'
# Accidentally try to import the module itself.
using module .\foo.psm1
function bar { 'hi from module foo' }
'# > foo.psm1
# This fails, because an infinite import loop is entered,
# eventually causing the nesting limit to be exceeded.
Import-Module .\foo.psm1
Why creating a (script) module with a manifest is a good idea:
While module manifests are optional - standalone *.psm1 files can serve as modules by themselves - there are good reasons to use them:
A module manifest is a *.psd1 file that accompanies your *.psm1 file and specifies important metadata, notably the version number, in the form of a hashtable literal; for the best user experience, both files should be placed in a directory of the same name (e.g., Foo.psm1 and its manifest, Foo.psd1, should be placed in a directory named Foo).
By using a manifested module, you enable several important use cases:
You need a manifest to properly support your module's software-development processes, notably version management.
It is also a prerequisite for supporting side-by-side installations of multiple versions of your module.
You need a manifest to automatically load associated resources, such as other modules or auxiliary .NET assemblies, and to define help resources.
You need a manifest in order to integrate with PowerShell's module auto-loading mechanism: If you place your properly manifested module into one of the directories listed in $env:PSModulePath, PowerShell will:
Discover the module and its commands even before the module is imported.
Will import it on demand, the first time you try to call a command from the session.
You need a manifest in order to publish a module to the official online repository for PowerShell modules, the PowerShell Gallery
To quickly outline the steps for creating a module with manifest:
Create a directory named for the base name of your .psm1 file; e.g., Foo
Place the script code as file Foo.psm1 file in that directory.
In the same directory, using the New-ModuleManifest cmdlet, create the manifest .psd1 file with the same base name (e.g., Foo.psd1)
At the very least, update the RootModule entry in the new .psd1 file to point to your .psm1 file (e.g., RootModule = 'Foo.psm1')
To integrate with the auto-loading feature, place your module directory in one of the locations listed in $env:PSModulePath; for the current user, that location is:
Windows PowerShell:
$HOME\Documents\WindowsPowerShell\Modules
PowerShell [Core] v6+:
Windows: $HOME\Documents\PowerShell\Modules
Linux, macOS: $HOME/.local/share/powershell/Modules
To support module discovery and auto-loading efficiently and to explicitly control and signal what a module exports, it is best to explicitly list the individual exported module members in the FunctionsToExport,
CmdletsToExport, VariablesToExport, and AliasesToExport entries of the manifest.
To make module creation easier, the community has provided helper modules:
Plaster is a "template-based file and project generator written in PowerShell", that can also be used to scaffold modules:
The built-in "New PowerShell Manifest Module" template scaffolds a module directory with all necessary files and support for Pester tests.
See this blog post for a walk-through.
Stucco builds on Plaster to provide an "opinionated Plaster template for building high-quality PowerShell modules."
Stucco is an advanced tool that goes beyond mere module creation, by scaffolding an entire project structure that includes psake tasks, scaffolding for CI/CD integration, licensing, and help authoring.
A quick example with Plaster:
# Install Plaster for the current user, if necessary.
Install-Module Plaster -Scope CurrentUser
# Get the template for creating a new script module.
$template = Get-PlasterTemplate | Where TemplatePath -match ScriptModule
# Scaffold a module in subdirectory 'Foo'
# * This will present a series of prompts, most of them with default values.
# * IMPORTANT: Be sure to also choose 'Foo' as the module *name* when prompted,
# so that the module auto-loading feature can discover your module
# (if placed in a dir. in $env:PSModulePath) and also so that you
# you can load it by its *directory* path; e.g., Import-Module ./Foo
Invoke-Plaster -TemplatePath $template.TemplatePath -Destination Foo
# Add a test function to the `.psm1` file.
# Note:
# * This is just for illustrative purposes. In real life, you would
# obviously use an editor to add functions to your module.
# * The function must be placed *before* the `Export-ModuleMember` call in order
# to be exported.
# * As stated, it is additionally recommended to list the exported members
# *explicitly*, one by one, in the *ToExport keys of the *.psd1 file.
(Get-Content -Raw ./Foo/Foo.psm1) -replace '\r?\n\r?\n', "`n`nfunction Get-Foo { 'Hi from module Foo.' }`n" | Set-Content -Encoding utf8 ./Foo/Foo.psm1
# Import the newly created module by its *directory* path.
# IMPORTANT:
# As stated, this assumes that you specified 'Foo' as the module name, i.e.
# that your manifest's file name is 'Foo.psd1', and your script module's
# 'Foo.psm1'.
Import-Module ./Foo -Verbose -Force
'---'
# Call the test function
Get-Foo
'---'
# Invoke the module's tests.
# Note: The scaffolding creates a single test to ensure that the
# module manifest (*.psd1) is valid.
Invoke-Pester ./Foo
You should see output such as the following:
VERBOSE: Loading module from path 'C:\Users\jdoe\Foo\Foo.psd1'.
VERBOSE: Loading module from path 'C:\Users\jdoe\Foo\Foo.psm1'.
VERBOSE: Importing function 'Get-Foo'.
---
Hi from module Foo.
---
____ __
/ __ \___ _____/ /____ _____
/ /_/ / _ \/ ___/ __/ _ \/ ___/
/ ____/ __(__ ) /_/ __/ /
/_/ \___/____/\__/\___/_/
Pester v4.9.0
Executing all tests in './Foo'
Executing script C:\Users\jdoe\Foo\test\Foo.Tests.ps1
Describing Module Manifest Tests
[+] Passes Test-ModuleManifest 128ms
Tests completed in 375ms
Tests Passed: 1, Failed: 0, Skipped: 0, Pending: 0, Inconclusive: 0
I have a simple powershell module containing a single function, an abridged version of this is as follows:
function My-Func
{
.
.
.
}
Export-ModuleMember -Function 'My-Func'
My manifest file contain a line to explicitly export this:
FunctionsToExport = "My-Func"
Everything uploads to the powershell gallery via Publish-Module without any problems, then when I come to install this and run Get-Module, I do not see the function in the export commands column of the output, also I when I attempt to call the function powershell tells me it does not exist.
I have a psm1 file for my module and a psd1 manifest, for some reason when I only see Manifest as the module type, I'm guessing I need to see script ?.
Any ideas ?
in your .psm1:
remove the Exported-ModuleMember line
in your .psd1:
RootModule = 'yourmodule.psm1'
FunctionsToExport = #('function1','function2')
the psd1 file FunctionsToExport works like the Exported-ModuleMember command. it's a cleaner way to define things from one centralized file.
Do you guys know, whether it's possible to convert PowerShell project consisting solely of functions, into module? What I want to achieve is to create distributable module of all my functions so others can use it. But without spending time of converting all functions into cmdlets.
Each of my functions is in separate file. When I then create *.psd1 and I try to include functions via 'FunctionsToExport', it doesn't work. I can't see my functions after module is loaded.
Is it even possible to export function from module when they're NOT (all of them) inside a .psm1 file? I'm still trying to figure out real differences and use of *psd1 and *psm1 files.
Yes, you can turn a bunch of .ps1 files into a module. Create a new folder in your module directory $env:USERPROFILE\Documents\WindowsPowerShell\Modules and put all the .ps1 files in that folder. Also create two text files <foldername>.psm1 and <foldername>.psd1 in the folder, so that you have a structure like this:
$env:USERPROFILE
`-Documents
`-WindowsPowerShell
`-Modules
`-MyModule
+-MyModule.psd1
+-MyModule.psm1
+-script1.ps1
+-script2.ps1
:
`-scriptN.ps1
Put the following line in the .psm1 file, so that it "imports" all .ps1 files:
Get-ChildItem -Path "$PSScriptRoot\*.ps1" | % { . $_.FullName }
and specify your metadata in the module manifest (the .psd1 file), e.g.:
#{
ModuleToProcess = 'MyModule.psm1'
ModuleVersion = '1.0'
GUID = '6bc2ac1e-2e88-4bc3-ac84-ecd16739b6aa'
Author = 'Matthew Lowe'
CompanyName = '...'
Copyright = '...'
Description = 'Description of your module.'
PowerShellVersion = '2.0'
FunctionsToExport = '*'
CmdletsToExport = ''
VariablesToExport = ''
AliasesToExport = ''
}
A GUID can be generated for instance via [guid]::NewGuid().
Here's a very simple way of doing it, without including your functions through dot sourcing mode, as it's done in the other answer:
Create a folder C:\MyModules.
Inside this folder, create an empty file named MyModules.PSM1.
Append to MyModules.PSM1 file, all functions (they don't need to be advanced) you want in the module.
YOU ARE DONE.
Now, you have a folder ( C:\MyModules ) that you must install in the target machine.
To install it in the target machine (per user), copy the folder C:\MyModule to the user's default module location (i.e. folder): $home\Documents\WindowsPowerShell\Modules.
Now, this user can type in any PowerShell session the first letter(s) of any function included in your module, that PowerShell's IntelliSense will recognize the function from your module (and uggest the completion substring).
If you don't like the name MyModule, you can change it, as long as you change the folder name as well as the PSM1 file name.
You can also opt to install your module for all users: help about_modules.