How do I invoke a powershell script to define a function so one can use it in the calling powershell script? - powershell

Suppose I have scripts AAA.ps1 and script BBB.ps1. Assume they are in the same location (directory).
Script BBB.ps1 defines a function BFunc which I want to invoke from AAA.ps1
How do I modify AAA.ps1 so that it can define-and-use BFunc?
As a test I have tried the following two commands in a script.
& "$PSScriptRoot\BBB.ps1"
get-childitem function:* | Select-Object Name | where { $_ -match "BFunc" }
If the above worked then I should see some output that BFunc is defined but nothing comes out.

Ah, sometimes it is too simple. It turns out that what PS expects is that BBB.ps1 be included into AAA.ps1. This means that the AAA.ps1 file could look like this:
. "$PSScriptRoot\BBB.ps1"
get-childitem function:* | Select-Object Name | where { $_ -match "BFunc" }
And that works!

Related

Powershell: ConvertFrom-Json doesn't get the variable assigned due to forward and backward compatibility

I am trying to load a variables into powershell from variables.json having the following
{
"psfp": "C:/San\/SV65\/ps",
"vmfp": "'C:/San\/SV65\/hyper-packer\/hyper-packer\/output-centos8-9\/Virtual Machines'",
"psmp": "C:/San\/SV65\/ps",
"vmname": ""
}
Trying to import with
$jvariables=Get-Content -Raw -Path '.\variables-ps.json' | ConvertFrom-Json
Output on powershell
PS C:\San\SV65\ps> Write-host $jvariables.psfp
C:/San/SV65/ps
PS C:\San\SV65\ps> Write-host $jvariables.vmfp
'C:/San/SV65/hyper-packer/hyper-packer/output-centos8-9/Virtual Machines'
PS C:\San\SV65\ps> Write-host $jvariables.psmp
C:/San/SV65/ps
These forward slash not incompatible on powershell for a windows path! ConvertFrom-Json doesn't accept backslash on the variables tried with '' as well
Hence could not load my modules on these path
any other way to achieve the above case?
Want to use json as it easy for the end user to update rather a txt files
Please share
you can loop through the object properties and change the path separator like this:
$jvariables.psobject.properties | where {$_.membertype -eq "NoteProperty"} |
foreach {$data.($_.name) = $_.value.replace("/","\")}
or a simpler approach would be to escape backslash in config file, like this :
{
"psfp": "C:\\San\\SV65\\ps",
"vmfp": "'C:\\San\\SV65\\hyper-packer\\hyper-packer\\output-centos8-9\\Virtual Machines'",
"psmp": "C:\\San\\SV65\\ps",
"vmname": ""
}

Is there a way to show all functions in a PowerShell script?

Is there any command to list all functions I've created in a script?
Like i created function doXY and function getABC or something like this.
Then I type in the command and it shows:
Function doXY
Function getABC
Would be a cool feature^^
Thanks for all your help.
You can have PowerShell parse your script, and then locate the function definitions in the resulting Abstract Syntax Tree (AST).
Get-Command is probably the easiest way to access the AST:
# Use Get-Command to parse the script
$myScript = Get-Command .\path\to\script.ps1
$scriptAST = $myScript.ScriptBlock.AST
# Search the AST for function definitions
$functionDefinitions = $scriptAST.FindAll({
$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]
}, $false)
# Report function name and line number in the script
$functionDefinitions |ForEach-Object {
Write-Host "Function '$($_.Name)' found on line $($_.StartLineNumber)!"
}
You can also use this to analyze the functions' contents and parameters if necessary.
Where your script is named things.ps1, something like...
cat ./things.ps1 | grep function
For MacOS/Linux or...
cat ./things.ps1 | select-string function
For Windows.
This is a built-in feature as shown in the PowerShell help files.
About_Providers
Similar questions have been asked before. So, this is a potential duplicate of:
How to get a list of custom Powershell functions?
Answers... Using the PSDrive feature
# To get a list of available functions
Get-ChildItem function:\
# To remove a powershell function
# removes `someFunction`
Remove-Item function:\someFunction
Or
Function Get-MyCommands {
Get-Content -Path $profile | Select-String -Pattern "^function.+" | ForEach-Object {
[Regex]::Matches($_, "^function ([a-z.-]+)","IgnoreCase").Groups[1].Value
} | Where-Object { $_ -ine "prompt" } | Sort-Object
}
Or this one
Get List Of Functions From Script
$currentFunctions = Get-ChildItem function:
# dot source your script to load it to the current runspace
. "C:\someScript.ps1"
$scriptFunctions = Get-ChildItem function: | Where-Object { $currentFunctions -notcontains $_ }
$scriptFunctions | ForEach-Object {
& $_.ScriptBlock
}
As for this...
Thanks, this is kind of what i want, but it also shows functions like
A:, B:, Get-Verb, Clear-Host, ...
That is by design. If you want it another way, then you have to code that.
To get name of functions in any script, it has to be loaded into memory first, then you can dot source the definition and get the internals. If you just want the function names, you can use regex to get them.
Or as simple as this...
Function Show-ScriptFunctions
{
[cmdletbinding()]
[Alias('ssf')]
Param
(
[string]$FullPathToScriptFile
)
(Get-Content -Path $FullPathToScriptFile) |
Select-String -Pattern 'function'
}
ssf -FullPathToScriptFile 'D:\Scripts\Format-NumericRange.ps1'
# Results
<#
function Format-NumericRange
function Flush-NumberBuffer
#>
This function will parse all the functions included in a .ps1 file, and then will return objects for each function found.
The output can be piped directly into Invoke-Expression to load the retuned functions into the current scope.
You can also provide an array of desired names, or a Regular Expression to constrain the results.
My use case was I needed a way for loading individual functions from larger scripts, that I don't own, so I could do pester testing.
Note: only tested in PowerShell 7, but I suspect it will work in older versions too.
function Get-Function {
<#
.SYNOPSIS
Returns a named function from a .ps1 file without executing the file
.DESCRIPTION
This is useful where you have a blended file containing functions and executed instructions.
If neither -Names nor -Regex are provided then all functions in the file are returned.
Returned objects can be piped directly into Invoke-Expression which will place them into the current scope.
Returns an array of objects with the following
- .ToString()
- .Name
- .Parameters
- .Body
- .Extent
- .IsFilter
- .IsWorkFlow
- .Parent
.PARAMETER -Names
Array of Strings; Optional
If provided then function objects of these names will be returned
The name must exactly match the provided value
Case Insensitive.
.PARAMETER -Regex
Regular Expression; Optional
If provided then function objects with names that match will be returned
Case Insensitive
.EXAMPLE
Get all the functions names included in the file
Get-Function -name TestA | select name
.EXAMPLE
Import a function into the current scope
Get-Function -name TestA | Invoke-Expression
#>
param (
$File = "c:\fullpath\SomePowerShellScriptFile.ps1"
,
[alias("Name", "FunctionNames", "Functions")]
$Names
,
[alias("NameRegex")]
$Regex
) # end function
# get the script and parse it
$Script = Get-Command /Users/royomi/Documents/dev/javascript/BenderBot_AI/Import-Function.ps1
$AllFunctions = $Script.ScriptBlock.AST.FindAll({$args[0] -is [Management.Automation.Language.FunctionDefinitionAst]}, $false)
# return all requested functions
$AllFunctions | Where-Object { `
( $Names -and $Names -icontains $_.Name ) `
-or ( $Regex -and $Names -imatch $Regex ) `
-or (-not $Names -and -not $Regex) `
} # end where-object
} # end function Get-Function

How to get the name of the previous in pipe cmdlet?

How to get the name of the previous in pipe cmdlet? For example:
gci myDir\*.ps1 | % { $prevCmdletName = ...?... }
resolve-Path myDir\*.ps1 | % { $prevCmdletName = ...?... }
gci myDir\*.ps1 | ? { $_.fullname -match 'tests' } | % { $prevCmdletName = ...?... }
test1.ps1, test2.ps1 | % { $prevCmdletName = ...?... }
Is there a common code to determinate the previous in pipe cmdlet? Is there a module with such functions?
Thanks.
What you may be after is a transcript start-transcript filepath\filename. It really depends on what the desired end result will be, but a transcript will indicate to you what commands are executing and what those commands are doing.
If you want your code to tell you what command you are running while you're running it, then that's a seemingly strange requirement - however, can be done.
gci c:\ | % {(Get-PSCallStack).Position.StartScriptPosition.GetFullScript()}
Doing this will obscure your output, but you can add it as one of the outputs. Getting creative with how you then use it to determine the precise previous argument in a pipeline, could be done by taking that string output and splitting it by the pipeline character. Whichever output you desire, you will need to encapsulate that in a script block via calculating which item in that split list you're after. Doing this all in one line as part of the pipe will yield undesired results.
It's unclear what you are after.
The previously iterated $PsItem/$_ in a ForEach?
Other than storing the previous in a variable?
$prevCmdletName = ""
Get-ChildItem myDir\*.ps1 | ForEach-Object {
"Current {0} previous {1}" -f $_,$prevCmdletName
$prevCmdletName=$_
}

Powershell prints variables out of order

I'm brand new to powershell, and I'm finding a very odd behavior when I try and pass a parameter into a function:
Code
Param(
[Parameter(Mandatory=$true)][string]$vhdSourceDir,
[Parameter(Mandatory=$true)][string]$vhdDestinationDir,
[Parameter(Mandatory=$true)][array]$hypervisorList,
[int]$vhdKeep = 10
)
function getDirectoryBuildNumber ($dir) {
# Returns a number like 627c6ddeb8776914 from a path like:
# c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir | Where-Object {$_.Name -match "^[A-Za-z0-9]{16}$" } | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
function findBoxFile ($dir, $build) {
echo "looking for build $build at $dir "
echo "the dir is $dir and the build is $build"
# e.g c:\BulidAgent\work\627c6ddeb8776914\packer\windows\box\hyperv\win2012r2std-cheflatest-001.box
return ( Get-ChildItem $dir\$build\packer\windows\box\hyperv\ | Where-Object Extension -in '.box' | Sort-Object LastAccessTime -Descending | Select-Object -First 1 )
}
Function Main ()
{
$THEBUILD=getDirectoryBuildNumber($vhdSourceDir)
echo "THEBUILD is $THEBUILD"
findBoxFile($vhdSourceDir,$THEBUILD)
#echo "BOXFILE is $BOXFILE"
}
main
Problem
Here are the parameters that I call the script with:
.\boxMove.ps1 -vhdSourceDir C:\BuildAgent\work -vhdDestinationDir e:\ -hypervisorList 'foobar'
Here is the output it generates
THEBUILD is 527c6ddeb8776914
looking for build at C:\BuildAgent\work 527c6ddeb8776914
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Get-ChildItem : Cannot find path 'C:\BuildAgent\work 527c6ddeb8776914\packer\windows\box\hyperv\' because it does not exist.
The parameters show up out of order. For example, the phrase 'looking for build should appear like so"
looking for build 527c6ddeb8776914 at C:\BuildAgent\work
but it shows up as
looking for build at C:\BuildAgent\work 527c6ddeb8776914
Also the phrase 'the dir is..' should read
the dir is C:\BuildAgent\work and the build is 527c6ddeb8776914
but it reads as
the dir is C:\BuildAgent\work 527c6ddeb8776914 and the build is
Why is powershell not printing the strings in order?
When you pass parameters to functions do not encapsulate them in parentheses and where multiple parameters are being passed, they should not be comma separated, instead a space should be used to separate them.
For instance:
findBoxFile($vhdSourceDir,$THEBUILD)
should read:
findBoxFile $vhdSourceDir $THEBUILD
Then you'll find that this removes the issue you were encountering with the incorrectly ordered output.
Firstly, clean up your function definitions. Instead of
function (params){code}
you should use
function
{
Param ($Param1,$param2)
code
}
Also, when accepting an array as a param, instead of using [array], specify the array type, (probably string in your case), so that would be (line 4)
[Parameter(Mandatory=$true)][string[]]$hypervisorList,
It's hard do say exactly why the script fails, but I'd start by cleaning up the obvious issues and test again.

Passing variables to subscripts

I was wondering if this was possible. I am trying to make a script we will refer to as a master script. This script queries a DB to get a list of servers we will call $svrs. Simple stuff.
The thing I don't know how to do or if it is possible is to run a series of subscripts from the master script using the $srvrs.Name variable as a parameter on those scripts.
$svrs = "get list sql stuff"
$scrpath = 'D:\test'
$scripts = Get-ChildItem $scrpath
$scripts.Name | ForEach-Object {
Invoke-Expression $_ {I have no idea how to get server name variable here}
}
Based on the comments, you do need a nested loop which won't be too complicated.
$Scripts | Select-object Name | % {$curScript = $_
$Servers | % {.\$_ $CurScript}
}
I ended up resolving this myself with #JNK 's assistance...
Here is how I got the result I needed.
$allServers | ForEach-Object {
$currentServer = $_
$scripts.Name | ForEach-Object {
Invoke-Expression ".\$_ $currentServer"
}
}