How do I load module from the project in a test script? - powershell

I'm having trouble loading getting the Module.psm1 to load in Module.tests.ps1.
Here is my Module.psm1. I add a return to Get-Function just to see if it would get picked up by the test runner.
This is in Module.psm1:
function Get-Function {
return $true
}
Module.psd1:
FunctionsToExport = 'Get-Function'
Module.tests.ps1:
Describe "Get-Function" {
Context "Function Exists" {
Import-Module .\Module.psm1
It "Should Return" {
Get-Function | Should Be $true
}
}
}
Does the module have to be built and loaded into my module path before I can write tests against it? Or is there a way to reference it's location relative to the test path?
The results I'm seeing in the out put window are:
------ Run test started ------
Describing Get-Function
Context Function Exists
[-] Should Return 731ms
The term 'Get-Function' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path
was included, verify that the path is correct and try again.
at line: 12 in C:\Users\Adam\Documents\code\Powershell\TestModuleProject\TestModuleProject\Module.tests.ps1
Tests completed in 731ms
Passed: 0 Failed: 1 Skipped: 0 Pending: 0
========== Run test finished: 1 run (0:00:08.1834099) ==========
Any suggestions?

I suspect that the working directory (.) is different from the directory in which your files reside when you're running the test. You could use $MyInvocation.MyCommand.Path to determine the directory of the test script.
Pester unit tests (which is what your test code and output look like) usually contain a line like this for that purpose:
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
Try changing your test code as follows:
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
Describe "Get-Function" {
Context "Function Exists" {
Import-Module "$here\Module.psm1"
It "Should Return" {
Get-Function | Should Be $true
}
}
}

Related

How to Mock Split-Path $script:MyInvocation.MyCommand.Path in Pester

Need help in Pester!
Script having one function, which calls an inline script using below
[string]$ScriptPath = Split-Path $script:MyInvocation.MyCommand.Path
. $ScriptPath/GetMemberList.ps1 -ErrorAction Stop
Pester class context:
Context "xxx returns proper result" {
BeforeEach {
Mock -Command Split-Path -MockWith { return "C:\xxxx\xxxx\Scripts" }
$Response = functionxxx #Params
}
It "Should return response" {
, $Response | Should -Not -Be $null
}
}
When I execute Pester, Giving me Below Error;
CommandNotFoundException: The term 'C:\PowerShell\Modules\Pester\5.2.2/GetMemberList.ps1' is not recognized as a name of a cmdlet, function, script file, or executable program.
Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
Can someone help me how Can I execute this pester?
Looks like your Split-Path Mock isn't working/isn't targeting correctly
When $script:MyInvocation.MyCommand.Path is called in script by pester it returns the Invocation path of pester, not the script. So it is calling . $ScriptPath/GetMemberList.ps1 -ErrorAction Stop with $scriptPath = C:\PowerShell\Modules\Pester\5.2.2, not the value of "C:\xxxx\xxxx\Scripts" you want - and clearly not finding your GetMemberList.ps1 script as that is not where it lives, hence the error.
If the function functionxxx you are testing is from an imported module, then make sure you add the -ModuleName flag followed by the the name of the module to the mock call, this should let it target correctly.

Win-PS2EXE - Respect the location of the resulting executable

Take these settings for the program Win-PS2EXE:
This is so that the console will show when the exe file is clicked on.
And this code:
$inf_file = "$PSScriptRoot\setup-files\install.inf"
write-host """$inf_file"""
timeout 10
Let us say that the path of the new executable is W:\Apps\Install Scheme.exe
Which means the $inf_file is here W:\Apps\setup-files\install.inf
When I click the converted exe file I get this.
Is there any way to get the correct path of W:\Apps\setup-files\install.inf so that the executable recognises the location of itself when clicked.
I thought that $PSScriptRoot would work.
I'm lost as to how to get around this as the exe file will eventually depend on knowing its location.
Here's the code that can accomplish that.
Function Get-PSScriptPath {
<#
.SYNOPSIS
Returns the current filepath of the .ps1 or compiled .exe with Win-PS2EXE.
.DESCRIPTION
This will return the path of the file. This will work when the .ps1 file is
converted with Win-PS2EXE
.NOTES
Author: Ste
Date Created: 2021.05.03
Tested with PowerShell 5.1 and 7.1.
Posted here: https://stackoverflow.com/q/60121313/8262102
.PARAMETER None
NA
.INPUTS
None. You cannot pipe objects to Get-PSScriptPath.
.OUTPUTS
Returns the current filepath of the .ps1 or compiled .exe with Win-PS2EXE.
.EXAMPLE (When run from a .ps1 file)
PS> Get-PSScriptPath
PS> C:\Users\Desktop\temp.ps1
.EXAMPLE (When run from a compiled .exe file with Win-PS2EXE.
PS> Get-PSScriptPath
PS> C:\Users\Desktop\temp.exe
#>
if ([System.IO.Path]::GetExtension($PSCommandPath) -eq '.ps1') {
$psScriptPath = $PSCommandPath
} else {
# This enables the script to be compiles and get the directory of it.
$psScriptPath = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName
}
return $psScriptPath
}
Get-PSScriptPath
To offer a pragmatic, concise alternative (PSv3+) that always reports the script path as a full path:
One-liner:
$scriptDir = if (-not $PSScriptRoot) { Split-Path -Parent (Convert-Path ([environment]::GetCommandLineArgs()[0])) } else { $PSScriptRoot }
Annotated form:
$scriptDir = if (-not $PSScriptRoot) { # $PSScriptRoot not defined?
# Get the path of the executable *as invoked*, via
# [environment]::GetCommandLineArgs()[0],
# resolve it to a full path with Convert-Path, then get its directory path
Split-Path -Parent (Convert-Path ([environment]::GetCommandLineArgs()[0]))
}
else {
# Use the automatic variable.
$PSScriptRoot
}

Can a function know from where it is exectuted?

$MyInvocation can be used to identify where the source of a script is located.
How can a function do the same?
function whereami {
Write-Host $MyInvocation.MyCommand.Path # produces nothing
}
Write-Host $MyInvocation.MyCommand.Path
whereami
The C programmer would use __FILE__.
There is $PSCommandPath which returns the full path to the script being executed.
Note that the script must be saved as a file before this will work in the ISE.
function whereami {
$PSCommandPath
}
Write-Host $MyInvocation.MyCommand.Path
whereami
Returns:
PS C:\Users\> C:\Users\somescript.ps1
C:\Users\somescript.ps1
C:\Users\somescript.ps1

-Verbose not working with my Pester Test in PowerShell

I wrote a pester test to check that certain folders and files exist. The pester test works great but I wanted to include suggestions for fixes if the test is called with the -Verbose option. But I can't seem to get the -Verbose parameter to the actual test.
Folder/File structure:
Custom-PowerShellModule
| Custom-PowerShellModule.psd1
| Custom-PowerShellModule.psm1
\---Tests
Module.Tests.ps1
Below is just the top part of pester test:
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
Describe "Module Minimum Requirements Tests. Use -Verbose for Suggested Fixes" -Tags Module {
Context "Test: Verify File Counts = 1" {
Write-Verbose "If you receive an error, verify there is only 'ONE' PSD1 File and only 'ONE' PSM1 File."
It "There is only one PSD1 file" { (Get-ChildItem "$Here\..\" *.psd1).count | Should be 1 }
It "There is only one PSM1 file" { (Get-ChildItem "$Here\..\" *.psm1).count | Should be 1 }
}
}
Per the other answer, it doesn't seem to be possible to use Write-Verbose when running the script with the Invoke-Pester command. I think this may be because using the Invoke-Pester command means that you script is interpreted rather than directly executed by the PowerShell engine. The next best alternative would be to add in If statements that perform the same checks as your tests and then use Write-Host or Write-Warning to give instructions if they are negative. I have done that occasionally in the past.
You can however use -verbose if you are executing the script directly (e.g just directly running the *.tests.ps1 file). However to do so you need to add [cmdletbinding()] and a Param block to the top of your script:
[cmdletbinding()]
Param()
$Here = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)"
Describe "Module Minimum Requirements Tests. Use -Verbose for Suggested Fixes" -Tags Module {
Context "Test: Verify File Counts = 1" {
Write-Verbose "If you receive an error, verify there is only 'ONE' PSD1 File and only 'ONE' PSM1 File."
It "There is only one PSD1 file" { (Get-ChildItem "$Here\..\" *.psd1).count | Should be 1 }
It "There is only one PSM1 file" { (Get-ChildItem "$Here\..\" *.psm1).count | Should be 1 }
}
}
Instead of explictly passing the Verbose flag to test cases, you can also change the default value of VerbosePreference in scope:
$VerbosePreference = $Env:MyVerbosePreference
Then you can control it from outside:
$Env:MyVerbosePreference= 'Continue'
Invoke-Pester ...
The -Verbose switch of the Invoke-Pester cmdlet is not available inside the test cases. You have to explicitly pass this for the test case to access.
Here is an example based on your script:
Param([Bool]$Verbose)
Describe "Module Minimum Requirements Tests. Use -Verbose for Suggested Fixes" -Tags Module {
Context "Test: Verify File Counts = 1" {
Write-Verbose "If you receive an error, verify there is only 'ONE' PSD1 File and only 'ONE' PSM1 File." -Verbose:$Verbose
It "There is only one PSD1 file" { (Get-ChildItem "$Here\..\" *.psd1).count | Should be 1 }
It "There is only one PSM1 file" { (Get-ChildItem "$Here\..\" *.psm1).count | Should be 1 }
}
}
Invoke-Pester -Script #{Path='path' ; Parameters = #{ Verbose = $True }}

How can I get the current PowerShell executing file?

Note: PowerShell 1.0
I'd like to get the current executing PowerShell file name. That is, if I start my session like this:
powershell.exe .\myfile.ps1
I'd like to get the string ".\myfile.ps1" (or something like that). EDIT: "myfile.ps1" is preferable.
Any ideas?
I've tried to summarize the various answers here, updated for PowerShell 5:
If you're only using PowerShell 3 or higher, use $PSCommandPath
If want compatibility with older versions, insert the shim:
if ($PSCommandPath -eq $null) { function GetPSCommandPath() { return $MyInvocation.PSCommandPath; } $PSCommandPath = GetPSCommandPath }
This adds $PSCommandPath if it doesn't already exist.
The shim code can be executed anywhere (top-level or inside a function), though $PSCommandPath variable is subject to normal scoping rules (eg, if you put the shim in a function, the variable is scoped to that function only).
Details
There's 4 different methods used in various answers, so I wrote this script to demonstrate each (plus $PSCommandPath):
function PSCommandPath() { return $PSCommandPath }
function ScriptName() { return $MyInvocation.ScriptName }
function MyCommandName() { return $MyInvocation.MyCommand.Name }
function MyCommandDefinition() {
# Begin of MyCommandDefinition()
# Note: ouput of this script shows the contents of this function, not the execution result
return $MyInvocation.MyCommand.Definition
# End of MyCommandDefinition()
}
function MyInvocationPSCommandPath() { return $MyInvocation.PSCommandPath }
Write-Host ""
Write-Host "PSVersion: $($PSVersionTable.PSVersion)"
Write-Host ""
Write-Host "`$PSCommandPath:"
Write-Host " * Direct: $PSCommandPath"
Write-Host " * Function: $(PSCommandPath)"
Write-Host ""
Write-Host "`$MyInvocation.ScriptName:"
Write-Host " * Direct: $($MyInvocation.ScriptName)"
Write-Host " * Function: $(ScriptName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Name:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Name)"
Write-Host " * Function: $(MyCommandName)"
Write-Host ""
Write-Host "`$MyInvocation.MyCommand.Definition:"
Write-Host " * Direct: $($MyInvocation.MyCommand.Definition)"
Write-Host " * Function: $(MyCommandDefinition)"
Write-Host ""
Write-Host "`$MyInvocation.PSCommandPath:"
Write-Host " * Direct: $($MyInvocation.PSCommandPath)"
Write-Host " * Function: $(MyInvocationPSCommandPath)"
Write-Host ""
Output:
PS C:\> .\Test\test.ps1
PSVersion: 5.1.19035.1
$PSCommandPath:
* Direct: C:\Test\test.ps1
* Function: C:\Test\test.ps1
$MyInvocation.ScriptName:
* Direct:
* Function: C:\Test\test.ps1
$MyInvocation.MyCommand.Name:
* Direct: test.ps1
* Function: MyCommandName
$MyInvocation.MyCommand.Definition:
* Direct: C:\Test\test.ps1
* Function:
# Begin of MyCommandDefinition()
# Note this is the contents of the MyCommandDefinition() function, not the execution results
return $MyInvocation.MyCommand.Definition;
# End of MyCommandDefinition()
$MyInvocation.PSCommandPath:
* Direct:
* Function: C:\Test\test.ps1
Notes:
Executed from C:\, but actual script is C:\Test\test.ps1.
No method tells you the passed invocation path (.\Test\test.ps1)
$PSCommandPath is the only reliable way, but was introduced in PowerShell 3
For versions prior to 3, no single method works both inside and outside of a function
While the current Answer is right in most cases, there are certain situations that it will not give you the correct answer. If you use inside your script functions then:
$MyInvocation.MyCommand.Name
Returns the name of the function instead name of the name of the script.
function test {
$MyInvocation.MyCommand.Name
}
Will give you "test" no matter how your script is named.
The right command for getting the script name is always
$MyInvocation.ScriptName
this returns the full path of the script you are executing. If you need just the script filename than this code should help you:
split-path $MyInvocation.PSCommandPath -Leaf
If you only want the filename (not the full path) use this:
$ScriptName = $MyInvocation.MyCommand.Name
Try the following
$path = $MyInvocation.MyCommand.Definition
This may not give you the actual path typed in but it will give you a valid path to the file.
beware:
Unlike the $PSScriptRoot and $PSCommandPath automatic variables, the
PSScriptRoot and PSCommandPath properties of the $MyInvocation automatic
variable contain information about the invoker or calling script, not the
current script.
e.g.
PS C:\Users\S_ms\OneDrive\Documents> C:\Users\SP_ms\OneDrive\Documents\DPM ...
=!C:\Users\S_ms\OneDrive\Documents\DPM.ps1
...where DPM.ps1 contains
Write-Host ("="+($MyInvocation.PSCommandPath)+"!"+$PSCommandPath)
If you are looking for the current directory in which the script is being executed, you can try this one:
$fullPathIncFileName = $MyInvocation.MyCommand.Definition
$currentScriptName = $MyInvocation.MyCommand.Name
$currentExecutingPath = $fullPathIncFileName.Replace($currentScriptName, "")
Write-Host $currentExecutingPath
As noted in previous responses, using "$MyInvocation" is subject to scoping issues and doesn't necessarily provide consistent data (return value vs. direct access value). I've found that the "cleanest" (most consistent) method for getting script info like script path, name, parms, command line, etc. regardless of scope (in main or subsequent/nested function calls) is to use "Get-Variable" on "MyInvocation"...
# Get the MyInvocation variable at script level
# Can be done anywhere within a script
$ScriptInvocation = (Get-Variable MyInvocation -Scope Script).Value
# Get the full path to the script
$ScriptPath = $ScriptInvocation.MyCommand.Path
# Get the directory of the script
$ScriptDirectory = Split-Path $ScriptPath
# Get the script name
# Yes, could get via Split-Path, but this is "simpler" since this is the default return value
$ScriptName = $ScriptInvocation.MyCommand.Name
# Get the invocation path (relative to $PWD)
# #GregMac, this addresses your second point
$InvocationPath = ScriptInvocation.InvocationName
So, you can get the same info as $PSCommandPath, but a whole lot more in the deal. Not sure, but it looks like "Get-Variable" was not available until PS3 so not a lot of help for really old (not updated) systems.
There are also some interesting aspects when using "-Scope" as you can backtrack to get the names, etc. of the calling function(s). 0=current, 1=parent, etc.
Hope this is somewhat helpful.
Ref, https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-variable
I would argue that there is a better method, by setting the scope of the variable $MyInvocation.MyCommand.Path:
ex> $script:MyInvocation.MyCommand.Name
This method works in all circumstances of invocation:
EX:
Somescript.ps1
function printme () {
"In function:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
}
"Main:"
( "MyInvocation.ScriptName: " + [string]($MyInvocation.ScriptName) )
( "script:MyInvocation.MyCommand.Name: " + [string]($script:MyInvocation.MyCommand.Name) )
( "MyInvocation.MyCommand.Name: " + [string]($MyInvocation.MyCommand.Name) )
" "
printme
exit
OUTPUT:
PS> powershell C:\temp\test.ps1
Main:
MyInvocation.ScriptName:
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: test.ps1
In function:
MyInvocation.ScriptName: C:\temp\test.ps1
script:MyInvocation.MyCommand.Name: test.ps1
MyInvocation.MyCommand.Name: printme
Notice how the above accepted answer does NOT return a value when called from Main. Also, note that the above accepted answer returns the full path when the question requested the script name only. The scoped variable works in all places.
Also, if you did want the full path, then you would just call:
$script:MyInvocation.MyCommand.Path
A short demonstration of #gregmac's (excellent and detailed) answer, which essentially recommends $PSCommandPath as the only reliable command to return the currently running script where Powershell 3.0 and above is used.
Here I show returning either the full path or just the file name.
Test.ps1:
'Direct:'
$PSCommandPath # Full Path
Split-Path -Path $PSCommandPath -Leaf # File Name only
function main () {
''
'Within a function:'
$PSCommandPath
Split-Path -Path $PSCommandPath -Leaf
}
main
Output:
PS> .\Test.ps1
Direct:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Within a function:
C:\Users\John\Documents\Sda\Code\Windows\PowerShell\Apps\xBankStatementRename\Test.ps1
Test.ps1
Did some testing with the following script, on both PS 2 and PS 4 and had the same result. I hope this helps people.
$PSVersionTable.PSVersion
function PSscript {
$PSscript = Get-Item $MyInvocation.ScriptName
Return $PSscript
}
""
$PSscript = PSscript
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
""
$PSscript = Get-Item $MyInvocation.InvocationName
$PSscript.FullName
$PSscript.Name
$PSscript.BaseName
$PSscript.Extension
$PSscript.DirectoryName
Results -
Major Minor Build Revision
----- ----- ----- --------
4 0 -1 -1
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
C:\PSscripts\Untitled1.ps1
Untitled1.ps1
Untitled1
.ps1
C:\PSscripts
This can works on most powershell versions:
(& { $MyInvocation.ScriptName; })
This can work for Scheduled Job
Get-ScheduledJob |? Name -Match 'JOBNAMETAG' |% Command