Skip to content

Instantly share code, notes, and snippets.

@nohwnd
Created February 17, 2019 19:04
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save nohwnd/509476b85f43b501033103d838c84789 to your computer and use it in GitHub Desktop.
Save nohwnd/509476b85f43b501033103d838c84789 to your computer and use it in GitHub Desktop.
Importing self-contained scripts. Demo for PSPowerHour.
function Import-Script {
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[String] $Path,
[Hashtable] $Parameters = @{},
[Object[]] $Arguments = @(),
[String] $EntryPoint = 'Main'
)
$sb = {
param ($__c, $__arguments, $__parameters)
Invoke-Expression "
function __placeholder () {
Write-Host ```
'This is a placeholder running instead of $($__c.EntryPoint)' ```
-ForegroundColor Yellow
}"
Set-Alias -Name $__c.EntryPoint -Value '__placeholder'
. $__c.Path @__arguments @__parameters
Remove-Item "alias:\$($__c.EntryPoint)" -Force
Remove-Item 'function:\__placeholder' -Force
Remove-Variable -Scope Local -Name '__c', '__arguments', '__parameters'
}
$__c = @{
Path = $Path
EntryPoint = $EntryPoint
}
Set-ScriptBlockScope -SessionState $PSCmdlet.SessionState -ScriptBlock $sb
. $sb $__c $Arguments $Parameters
}
function Set-ScriptBlockScope {
param (
[Parameter(Mandatory)]
[Management.Automation.SessionState] $SessionState,
[Parameter(Mandatory)]
[ScriptBlock] $ScriptBlock
)
$flags = [System.Reflection.BindingFlags]'Instance,NonPublic'
$SessionStateInternal = $SessionState.GetType().GetProperty('Internal', $flags).GetValue($SessionState, $null)
# attach the original session state to the wrapper scriptblock
# making it invoke in the caller session state
$ScriptBlock.GetType().GetProperty('SessionStateInternal', $flags).SetValue($ScriptBlock, $SessionStateInternal, $null)
}
Export-ModuleMember -Function Import-Script
param (
[Parameter(Mandatory)]
$Name
)
# our entry point function that holds
# all the stuff that should happen when
# we run this script
function Main {
param (
[Parameter(Mandatory)]
$Name
)
Get-Greeting $Name
}
# internal function that writes greeting
function Get-Greeting {
param ($Name)
$text = Get-GreetingText
$formattedGreetingText = $text -f $Name
Write-Host $formattedGreetingText -ForegroundColor Magenta
$formattedGreetingText
}
# another internal function that collaborates
# with Get-Greeting and that we will mock
# in our tests
function Get-GreetingText {
'Hello, {0}!'
}
Main -Name $Name
Get-Module Pester, Import-Script | Remove-Module
Import-Module Pester -MinimumVersion 4.0
Import-Module $PSScriptRoot\Import-Script.psm1
function prompt () { "> " }
cd $PSScriptRoot
"Initialized, run the script step by step 🚀"
break
Import-Script -Path .\Script.ps1 -EntryPoint 'Main' -Parameters @{ Name = 'Jakub' }
Describe "t1.ps1" {
It "Can test 'Main' function" {
Main -Name 'PSPowerHour' | Should -Be 'Hello, PSPowerHour!'
}
It "Can test 'Get-Greeting' function" {
Get-Greeting -Name 'PSPowerHour' | Should -Be 'Hello, PSPowerHour!'
}
It "Can mock Get-GreetingText that is used by Get-GreetingFunction" {
Mock Get-GreetingText { 'I ❤ {0}!' }
Get-Greeting -Name 'PSPowerHour' | Should -Be 'I ❤ PSPowerHour!'
}
}
break
# mock the entrypoint function so
# we don't run anything from the script
$EntryPoint = 'Main'
$Path = ".\Script.ps1"
function placeholder {
Write-Host "this is a placeholder" -ForegroundColor Cyan
}
# alias to prevent main from running
Set-Alias $EntryPoint 'placeholder'
. $Path -Name 'Jakub'
# remove the alias, now we have all functions
# from the script dot-sourced in this scope
Remove-Item "alias:$EntryPoint" -Force
Main -Name "Jakub"
break
# why is this magic
# this is like a function call
# function calls make new scope
& {
# this is like dot-sourcing
function a () { "hello" }
}
# the function won't exist outside of the previous scriptblock,
# because it dies with the end of the scope
a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment