Making a PowerShell script portable with its dependencies - powershell

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
}
}

Related

Why is import-module or dot sourcing a file not working in powershell scripts?

I am developing and running PS scripts in VS Code using the PowerShell Extension. I have defined a number of functions in a separate PS module file, which I have saved here:
C:\Users\MyName\Documents\PowerShell\Modules\PowerBiFunctions\PowerBiFunctions.psm1
But when attempting to import the module with
Import-Module PowerBiFunctions
or a copy placed in the same directory as the script
Import-Module $PSScriptRoot\PowerBiFunctions.psm1
I get the following error:
Import-Module : The specified module 'PowerBiFunctions' was not loaded because no valid module file was found in any module directory.
In addition, I have made a copy of the file with the .ps1 extension and placed in the same directory. However, I can't seem to include it with simple dot sourcing:
.\PowerBiFunctions.ps1
or
. .\PowerBiFunctions.ps1
What seems to be the problem?
Create a module manifest:
# cd to module folder
Set-Location C:\Users\MyName\Documents\PowerShell\Modules\PowerBiFunctions\
# create new manifest file
New-ModuleManifest .\PowerBiFunctions.psd1 -RootModule .\PowerBiFunctions.psm1 -FunctionsToExport list,of,exported,function,names
Now you can import it by name:
Import-Module PowerBiFunctions

In Powershell is it possible to publish module to an in-memory storage?

Motivation
I need to test my custom module loading logic. Publishing a test module for real and then installing it for real is just too time consuming in Powershell.
So, instead of publishing a test module to a real directory I was wondering if there is some kind of in-memory provider that can satisfy the Powershell module functions, like Publish/Install/Import-Module.
EDIT 1
Basically, I want the same thing as Simple in-memory PowerShell Provider?
EDIT 2
I specifically need the following functions to work with the in-memory storage:
Install-Module
Find-Module
Get-Module with or without -ListAvailable
Import-Module
Importing a psm1 file does not cut it. Creating a module with New-Module does not cut it either.
you can use
Import-Module "Path to your psm1 file"
for testing purpose when it is in under development.
I have previously just had test modules setup with a PSD1 and PSM1 file and then dropped the PS1 file into the directory when it is ready for testing. As long as you have the export functions section of the PDS1 set to * it should load your PS1 file when you import the module.
The other way would be to simply save your function as a PS1 file and then Dot Source it. For example:
. C:\Temp\TestFunction.ps1
Note the period then the space. This will load the function into memory and will be gone after you restart the session.
You can't do this entirely in memory but you can use a directory. Possibly you might be able to create a ramdisk or similar, but a regular directory on an SSD should be quick enough for most purposes. Run
# register the repo
Register-PSRepository -Name MyRepo -SourceLocaton c:\mydirectory
# publish to it
Publish-Module -Name MyModule -Repository MyRepo
# install from it
Install-Module -Name MyModule -Repoitory MyRepo

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

Powershell import-module doesn't find modules

I'm learning PowerShell and I'm trying to build my own module library.
I've written a simple module XMLHelpers.psm1 and put in my folder $home/WindowsPowerShell/Modules.
When I do:
import-module full_path_to_XMLHelpers.psm1
It works. But when I do:
import-module XMLHelpers
It doesn't work and I get the error:
Import-Module : The specified module 'xmlhelpers' was not loaded because no valid module file was found in any module directory.
I've checked that the environment variable PSModulePath contains this folder. As it is a network folder, I've also tried to move it to a local folder and to modify PSModulePath but without success
$env:PSModulePath=$env:PSModulePath+";"+'C:\local'
Any idea on what could cause this issue?
The module needs to be placed in a folder with the same name as the module. In your case:
$home/WindowsPowerShell/Modules/XMLHelpers/
The full path would be:
$home/WindowsPowerShell/Modules/XMLHelpers/XMLHelpers.psm1
You would then be able to do:
import-module XMLHelpers
1.This will search XMLHelpers/XMLHelpers.psm1 in current folder
Import-Module (Resolve-Path('XMLHelpers'))
2.This will search XMLHelpers.psm1 in current folder
Import-Module (Resolve-Path('XMLHelpers.psm1'))
I think that the Import-Module is trying to find the module in the default directory C:\Windows\System32\WindowsPowerShell\v1.0\Modules.
Try to put the full path, or copy it to C:\Windows\System32\WindowsPowerShell\v1.0\Modules
I experienced the same error and tried numerous things before I succeeded. The solution was to prepend the path of the script to the relative path of the module like this:
// Note that .Path will only be available during script-execution
$ScriptPath = Split-Path $MyInvocation.MyCommand.Path
Import-Module $ScriptPath\Modules\Builder.psm1
Btw you should take a look at http://msdn.microsoft.com/en-us/library/dd878284(v=vs.85).aspx which states:
Beginning in Windows PowerShell 3.0, modules are imported automatically when any cmdlet or function in the module is used in a command. This feature works on any module in a directory that this included in the value of the PSModulePath environment variable ($env:PSModulePath)
I had this problem, but only in Visual Studio Code, not in ISE. Turns out I was using an x86 session in VSCode. I displayed the PowerShell Session Menu and switched to the x64 session, and all the modules began working without full paths. I am using Version 1.17.2, architecture x64 of VSCode. My modules were stored in the C:\Windows\System32\WindowsPowerShell\v1.0\Modules directory.
Some plugins require one to run as an Administrator and will not load unless one has those credentials active in the shell.
My finding with PS 5.0 on Windows 7: $ENV:PsModulePath has to end with a . This normally means it will load all modules in that path.
I'm not able to add a single module to $env:PsModulePath and get it to load with Import-Module ExampleModule. I have to use the full path to the module. e.g. C:\MyModules\ExampleModule. I am sure it used to work.
For example:
Say I have the modules:
C:\MyModules\ExampleModule
C:\MyModules\FishingModule
I need to add C:\MyModules\ to $env:PsModulePath, which will allow me to do
Import-Module ExampleModule
Import-Module FishingModule
If for some reason, I didn't want FishingModule, I thought I could add C:\MyModules\ExampleModule only (no trailing \), but this doesn't seem to work now. To load it, I have to Import-Module C:\MyModules\ExampleModule
Interestingly, in both cases, doing Get-Module -ListAvailable, shows the modules, but it won't import. Although, the module's cmdlets seem to work anyway.
AFAIK, to get the automatic import to work, one has to add the name of the function to FunctionsToExport in the manifest (.psd1) file. Adding FunctionsToExport = '*', breaks the auto load. You can still have Export-ModuleMember -Function * in the module file (.psm1).
These are my findings. Whether there's been a change or my computer is broken, remains to be seen. HTH
try with below on powershell:
Set-ExecutionPolicy -ExecutionPolicy Unrestricted
import-module [\path\]XMLHelpers.psm1
Instead of [] put the full path
Full explanation of this and that
First of all check Your account type,some imports are not allowed to normal partner accounts.