Get path to file that defines PowerShell function - powershell

I'm trying to figure out a way of getting the file path where a PowerShell function is defined (eg. Test1 or Test2), rather than the caller's path, which would be easily obtained via the $PSScriptRoot automatic variable.
Consider the following folder structure:
c:\Scripts\Test.ps1
c:\Scripts\Test1\Test1.ps1
c:\Scripts\Test2\Test2.ps1
Test.ps1
Set-Location $PSScriptRoot;
. .\Test1\Test1.ps1;
. .\Test2\Test2.ps1;
Test1;
Test2;
Test1.ps1
function Test1 {
[CmdletBinding()]
param (
)
Write-Host -Object "Entering Test1";
Write-Host -Object "Exiting Test1";
}
Test2.ps1
function Test2 {
[CmdletBinding()]
param (
)
Write-Host -Object "Test2";
Write-Host -Object "Exiting Test2";
}
I have tried using a variety of properties on the $PSCmdlet and $MyInvocation automatic variables, but cannot seem to find a way to obtain the path to the file where the function is defined, rather than where the caller is located.
Asked differently, how would I get the path C:\Scripts\Test1\Test1.ps1 from inside the Test1 function, when it's called from Test.ps1? The same goes for the Test2.ps1 script, and Test2 function. How would I get the path C:\Scripts\Test2\Test2.ps1 from inside the Test2 function?
Is this not possible because I'm using the . call operator, to import the functions into the current session?

Here's another way, get the file that contains the function using function's scriptblock File property:
${function:Test1}.File

I think $PSCommandPath has what you're looking for.

Related

How to create an alias with fixed/static parameters in Powershell [duplicate]

I'm trying to set up a Windows PowerShell alias to run MinGW's g++ executable with certain parameters. However, these parameters need to come after the file name and other arguments. I don't want to go through the hassle of trying to set up a function and all of that. Is there a way to simply say something like:
alias mybuild="g++ {args} -lib1 -lib2 ..."
or something along those lines? I am not all that familiar with PowerShell, and I'm having a difficult time finding a solution. Anyone?
You want to use a function, not an alias, as Roman mentioned. Something like this:
function mybuild { g++ $args -lib1 -lib2 ... }
To try this out, here's a simple example:
PS> function docmd { cmd /c $args there }
PS> docmd echo hello
hello there
PS>
You might also want to put this in your profile in order to have it available whenever you run PowerShell. The name of your profile file is contained in $profile.
There is not such a way built-in. IMHO, a wrapper function is the best way to go so far. But I know that some workarounds were invented, for example:
https://web.archive.org/web/20120213013609/http://huddledmasses.org/powershell-power-user-tips-bash-style-alias-command
To build an function, store it as an alias, and persist the whole thing in your profile for later, use:
$g=[guid]::NewGuid();
echo "function G$g { COMMANDS }; New-Alias -Force ALIAS G$g">>$profile
where you have replaced ALIAS with the alias you want and COMMANDS with the command or string of commands to execute.
Of course, instead of doing that you can (and should!) make an alias for the above by:
echo 'function myAlias {
$g=[guid]::NewGuid();
$alias = $args[0]; $commands = $args[1]
echo "function G$g { $commands }; New-Alias -Force $alias G$g">>$profile
}; New-Alias alias myAlias'>>$profile
Just in case your brain got turned inside out from all the recursion (aliasing of aliases, etc.), after pasting the second code block to your PowerShell (and restarting PowerShell), a simple example of using it is:
alias myEcho 'echo $args[0]'
or without args:
alias myLs 'ls D:\MyFolder'
Iff you don't have a profile yet
The above method will fail if you don't have a profile yet!
In that case, use New-Item -type file -path $profile -force from this answer.
This is a sample function that will do different things based on how it was called:
Function Do-Something {
[CmdletBinding()]
[Alias('DOIT')]
Param(
[string] $option1,
[string] $option2,
[int] $option3)
#$MyInvocation|select *|FL
If ($MyInvocation.InvocationName -eq 'DOIT'){write-host "You told me to do it...so i did!" -ForegroundColor Yellow}
Else {Write-Host "you were boring and said do something..." -ForegroundColor Green}
}
Creating a 'filter' is also an option, a lighter alternative to functions. It processes each element in the pipeline, assigning it the $_ automatic variable. So, for instance:
filter test { Write-Warning "$args $_" }
'foo','bar' | test 'This is'
returns:
WARNING: This is foo
WARNING: This is bar

PowerShell: provide parameters in a file

Is there a way to provide powershell parameters with a file?
At the moment I have a script which is called My_Script.ps1. To start this script I have to provide the right parameters in the command:
.\My_Script.ps1 -param1="x" -param2="x" -param3="x" -param4="x" -param5="x" -param6="x" ...
This works but it isn't a very easy way to start the script. Is it possible in powershell to use a file in which you store your parameters and to use that file when you start the script?
Example
In My_Script.ps1 I add something like:
Param(
[string]$File="Path/to/file"
)
In my file I have something like
param1="x"
param2="x"
param3="x"
param4="x"
...
To execute the script you can edit the file and just start the script with .\My_Script.ps1
Another option:
Just use a ps1 file as config file and define your variables as you would do in your main script
$Param1 = "Value"
$Param2 = 42
Then you can use dot-sourcing or import-module to get the data from the config file
. .\configfile.ps1
or
Import-Module .\Configfile.ps1
afterwards you can just use the variables
In addition to splatting you can create variables from = separated values in a file.
param1=foo
param2=bar
param3=herp
param4=derp
Don't quote the values. The parameter names should be valid for a variable (no spaces etc.)
PowerShell 3 and newer:
(Get-Content c:\params.ini -raw | ConvertFrom-StringData).GetEnumerator() |
ForEach { Set-Variable $_.name $_.value }
PowerShell 2:
([IO.File]::ReadAllText('c:\params.ini') | ConvertFrom-StringData).GetEnumerator() |
ForEach { Set-Variable $_.name $_.value }
The code creates variables in current scope. It's possible to create in a global/script/parent scope.
You can use this blog posting
for a start and declare your parameters in an ini-like format.
For sure you could also use a csv-like format and work with import-csv cmdlet.

Creating functions dynamically in a module in PowerShell

Suppose I have the following code in a module (called MyModule.psm1, in the proper place for a module):
function new-function{
$greeting='hello world'
new-item -path function:\ -name write-greeting -value {write-output $greeting} -Options AllScope
write-greeting
}
After importing the module and running new-function I can successfully call the write-greeting function (created by new-function).
When I try to call the write-greeting function outside the scope of the new-function call, it fails because the function does not exist.
I've tried dot-sourcing new-function, but that doesn't help. I've supplied the -option Allscope, but apparently that only includes it in child scopes.
I've also tried explicitly following the new-item call with an export-modulemember write-greeting which doesn't give an error, but also doesn't create the function.
I want to be able to create a function dynamically (i.e. via new-item because the contents and name of the function will vary based on input) from a function inside a module and have the newly created function available to call outside of the module.
Specifically, I want to be able to do this:
Import-module MyModule
New-Function
write-greeting
and see "hello world" as output
Any ideas?
Making the function visible is pretty easy: just change the name of your function in New-Item to have the global: scope modifier:
new-item -path function:\ -name global:write-greeting -value {write-output $greeting} #-Options AllScope
You're going to have a new problem with your example, though, because $greeting will only exist in the new-function scope, which won't exist when you call write-greeting. You're defining the module with an unbound scriptblock, which means it will look for $greeting in its scope (it's not going to find it), then it will look in any parent scopes. It won't see the one from new-function, so the only way you'll get any output is if the module or global scope contain a $greeting variable.
I'm not exactly sure what your real dynamic functions will look like, but the easiest way to work around the new issue is to create a new closure around your scriptblock like this:
new-item -path function:\ -name global:write-greeting -value {write-output $greeting}.GetNewClosure()
That will create a new dynamic module with a copy of the state available at the time. Of course, that creates a new problem in that the function won't go away if you call Remove-Module MyModule. Without more information, I'm not sure if that's a problem for you or not...
You were close with needing to dot source, but you were missing Export-ModuleMember. Here is a complete example:
function new-function
{
$greeting='hello world'
Invoke-Expression "function write-greeting { write-output '$greeting' }"
write-greeting
}
. new-function
Export-ModuleMember -Function write-greeting
You also did not need or want -Scope AllScope.
Using the global: scope qualifier appears to work, but isn't the ideal solution. First, your function could stomp on another function in the global scope, which modules normally shouldn't do. Second, your global function would not be removed if you remove the module. Last - your global function won't be defined in the scope of the module, so if it needed access to non-exported functions or variables in your module, you can't (easily) get at them.
Thanks to the other solutions i was able to come up with a little helper that allows me to add plain script-files as functions and export them for the module in one step.
I have added the following function to my .psm1
function AddModuleFileAsFunction {
param (
[string] $Name,
[switch] $Export
)
$content = Get-Content (Join-Path $PSScriptRoot "$Name.ps1") -Raw
# Write-Host $content
$expression = #"
function $Name {
$content
}
"#
Invoke-Expression $expression
if ($Export) {
Export-ModuleMember -Function $Name
}
}
this allows me to load scripts as functions:
. AddModuleFileAsFunction "Get-WonderfulThings" -Export
( loads Get-WonderfulThings.ps1 body and exports it as function:Get-WonderfulThings )

How to pass output from a PowerShell cmdlet to a script?

I'm attempting to run a PowerShell script with the input being the results of another PowerShell cmdlet. Here's the cross-forest Exchange 2013 PowerShell command I can run successfully for one user by specifying the -Identity parameter:
.\Prepare-MoveRequest.ps1 -Identity "user#domain.com" -RemoteForestDomainController "dc.remotedomain.com" $Remote -UseLocalObject -OverwriteLocalObject -Verbose
I want to run this command for all MailUsers. Therefore, what I want to run is:
Get-MailUser | select windowsemailaddress | .\Prepare-MoveRequest.ps1 -RemoteForestDomainController "dc.remotedomain.com" $Remote -LocalForestDomainController "dc.localdomain.com" -UseLocalObject -OverwriteLocalObject -Verbose
Note that I removed the -Identity parameter because I was feeding it from each Get-MailUser's WindowsEmailAddress property value. However, this returns with a pipeline input error.
I also tried exporting the WindowsEmailAddress property values to a CSV, and then reading it as per the following site, but I also got a pipeline problem: http://technet.microsoft.com/en-us/library/ee861103(v=exchg.150).aspx
Import-Csv mailusers.csv | Prepare-MoveRequest.ps1 -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote
What is the best way to feed the windowsemailaddress field from each MailUser to my Prepare-MoveRequest.ps1 script?
EDIT: I may have just figured it out with the following foreach addition to my Import-Csv option above. I'm testing it now:
Import-Csv mailusers.csv | foreach { Prepare-MoveRequest.ps1 -Identity $_.windowsemailaddress -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote }
You should declare your custom function called Prepare-MoveRequest instead of simply making it a script. Then, dot-source the script that declares the function, and then call the function. To accept pipeline input into your function, you need to declare one or more parameters that use the appropriate parameter attributes, such as ValueFromPipeline or ValueFromPipelineByPropertyName. Here is the official MSDN documentation for parameter attributes.
For example, let's say I was developing a custom Stop-Process cmdlet. I want to stop a process based on the ProcessID (or PID) of a Windows process. Here is what the command would look like:
function Stop-CustomProcess {
# Specify the CmdletBinding() attribute for our
# custom advanced function.
[CmdletBinding()]
# Specify the PARAM block, and declare the parameter
# that accepts pipeline input
param (
[Parameter(ValueFromPipelineByPropertyName = $true)]
[int] $Id
)
# You must specify the PROCESS block, because we want this
# code to execute FOR EACH process that is piped into the
# cmdlet. If we do not specify the PROCESS block, then the
# END block is used by default, which only would run once.
process {
Write-Verbose -Message ('Stopping process with PID: {0}' -f $ID);
# Stop the process here
}
}
# 1. Launch three (3) instances of notepad
1..3 | % { notepad; };
# 2. Call the Stop-CustomProcess cmdlet, using pipeline input
Get-Process notepad | Stop-CustomProcess -Verbose;
# 3. Do an actual clean-up
Get-Process notepad | Stop-Process;
Now that we've taken a look at an example of building the custom function ... once you've defined your custom function in your script file, dot-source it in your "main" script.
# Import the custom function into the current session
. $PSScriptRoot\Prepare-MoveRequest.ps1
# Call the function
Get-MailUser | Prepare-MoveRequest -RemoteForestDomainController dc.remotedomain.com $Remote -LocalForestDomainController dc.localdomain.com -UseLocalObject -OverwriteLocalObject -Verbose;
# Note: Since you've defined a parameter named `-WindowsEmailAddress` that uses the `ValueFromPipelineByPropertyName` attribute, the value of each object will be bound to the parameter, as it passes through the `PROCESS` block.
EDIT: I would like to point out that your edit to your post does not properly handle parameter binding in PowerShell. It may achieve the desired results, but it does not teach the correct method of binding parameters in PowerShell. You don't have to use the ForEach-Object to achieve your desired results. Read through my post, and I believe you will increase your understanding of parameter binding.
My foreach loop did the trick.
Import-Csv mailusers.csv | foreach { Prepare-MoveRequest.ps1 -Identity $_.windowsemailaddress -RemoteForestDomainController DC.remotedomain.com -RemoteForestCredential $Remote }

How can I write a PowerShell alias with arguments in the middle?

I'm trying to set up a Windows PowerShell alias to run MinGW's g++ executable with certain parameters. However, these parameters need to come after the file name and other arguments. I don't want to go through the hassle of trying to set up a function and all of that. Is there a way to simply say something like:
alias mybuild="g++ {args} -lib1 -lib2 ..."
or something along those lines? I am not all that familiar with PowerShell, and I'm having a difficult time finding a solution. Anyone?
You want to use a function, not an alias, as Roman mentioned. Something like this:
function mybuild { g++ $args -lib1 -lib2 ... }
To try this out, here's a simple example:
PS> function docmd { cmd /c $args there }
PS> docmd echo hello
hello there
PS>
You might also want to put this in your profile in order to have it available whenever you run PowerShell. The name of your profile file is contained in $profile.
There is not such a way built-in. IMHO, a wrapper function is the best way to go so far. But I know that some workarounds were invented, for example:
https://web.archive.org/web/20120213013609/http://huddledmasses.org/powershell-power-user-tips-bash-style-alias-command
To build an function, store it as an alias, and persist the whole thing in your profile for later, use:
$g=[guid]::NewGuid();
echo "function G$g { COMMANDS }; New-Alias -Force ALIAS G$g">>$profile
where you have replaced ALIAS with the alias you want and COMMANDS with the command or string of commands to execute.
Of course, instead of doing that you can (and should!) make an alias for the above by:
echo 'function myAlias {
$g=[guid]::NewGuid();
$alias = $args[0]; $commands = $args[1]
echo "function G$g { $commands }; New-Alias -Force $alias G$g">>$profile
}; New-Alias alias myAlias'>>$profile
Just in case your brain got turned inside out from all the recursion (aliasing of aliases, etc.), after pasting the second code block to your PowerShell (and restarting PowerShell), a simple example of using it is:
alias myEcho 'echo $args[0]'
or without args:
alias myLs 'ls D:\MyFolder'
Iff you don't have a profile yet
The above method will fail if you don't have a profile yet!
In that case, use New-Item -type file -path $profile -force from this answer.
This is a sample function that will do different things based on how it was called:
Function Do-Something {
[CmdletBinding()]
[Alias('DOIT')]
Param(
[string] $option1,
[string] $option2,
[int] $option3)
#$MyInvocation|select *|FL
If ($MyInvocation.InvocationName -eq 'DOIT'){write-host "You told me to do it...so i did!" -ForegroundColor Yellow}
Else {Write-Host "you were boring and said do something..." -ForegroundColor Green}
}
Creating a 'filter' is also an option, a lighter alternative to functions. It processes each element in the pipeline, assigning it the $_ automatic variable. So, for instance:
filter test { Write-Warning "$args $_" }
'foo','bar' | test 'This is'
returns:
WARNING: This is foo
WARNING: This is bar