Wrap existing powershell script as function? - function

I have a script like this:
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
$Message
)
Write-Host "Hello, $Message!"
But I want to provide a method that allows users to dot source the script. Initially, I was just thinking I would wrap the whole script in a function to allow dot sourcing:
function Hello
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
$Message
)
Write-Host "Hello, $Message!"
}
The problem with this is that if I did this, it would break the script for some people who are using it the old way. Is it possible to return an object with the same functionality as the script, or is there a way to dot source the script as it is (the first example above)?

I would suggest using splatting with $PSBoundParameters:
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
$Message
)
function Hello
{
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
$Message
)
Write-Host "Hello, $Message!"
}
Hello #PSBoundParameters
Alternatively: instead of dot-sourcing just create function from existing script:
$code = Get-Content .\HelloScript.ps1
New-Item -Path function:\Hello -Value ([scriptblock]::Create($code))

At the end of function Hello you must trigger it to simulate old behaviour. So if the script used to take an argument from command line, lets say c:\scripts\hello.ps1 bob, you can do it as so:
function Hello
{
...
}
if($args.Count -eq 0){
Hello $null
}
else{
Hello $args
}
$args will be an empty array if you were to run c:\scripts\newhello.ps1 without an argument, rather than $null as previously when no value was passed. Therefore we check $args.Count before triggering the function with correct parameter.

I know this is incredibly old but I just ran into it while doing something similar. I have a Foo.ps1 script that needs to work as a script but that I would also like available as one function exported from a module. It seems (I just started playing with it) as if putting the following (in Foo.psm1) function Foo { . "$PSScriptRoot\Foo.ps1" } works like a charm. Avoids having to write out all the arguments in two places.

Related

Need Help - First Function

I'm trying to write my first function and am having some issues. When I run the below I get no output. I feel like I'm missing something obvious but I'm not sure what.
function findModifiedFiles {
[CmdletBinding()]
param (
[string]$dir,
[int]$days
)
Process {
Write-Host "Directory: " $dir
Write-Host "Days: "$days
}
}
Output:
You ultimately need to load your function and then call the function to receive any output. Since your function is defined in a file, one way to load the function is by dot sourcing the file. Then you can simply call your function.
. .\modfilesTest.ps1
findModifiedFiles -dir c:\temp -days 7
An alternative is to not use a function at all just run the script with parameters. If we edit your file to contain the following, we can just call the script afterwards.
# modfilesTest.ps1 Contents
[CmdletBinding()]
param (
[string]$dir,
[int]$days
)
Process {
Write-Host "Directory: " $dir
Write-Host "Days: "$days
}
Now call the script with your parameters.
.\modfilesTest.ps1 -dir c:\temp -days 7
A third alternative is to just paste a function definition into your console. At that point, the function is loaded into your current scope. Then you can just call the function.

PowerShell adds other values to return value of function

It seems that PowerShell adds an additional variable to the return value of a function.
The function subfoo2 itself delivers the correct values, but as soon as PowerShell jumps back to the postion where I called the function (in foo1), value contains the value of an other variable ($msg)
(Have a look at the comments in the code)
writeMessageLog($msg){
...
Add-Content $msg
...
}
subfoo2{
writeMessageLog($msg)
return $UserArrayWithValues #During Debug, $Array is fine (1)
}
foo1{
$var = subfoo2 $UserArray # $var has now the value of $msg and $UserArrayWithValues (2)
#do something with var
}
Realcode:
function WriteLog
{
param ( [string] $severity , $msgNumber, [string] $msg )
...
$msgOut = $date + ... + $msg
Add-Content $msgout ( $msgOut )
...
}
function getFeatures
{
writelog 'I' 1002 $true $true "Load Features"
$Features = importCsv -pPath $FeatureDefintionFilePath
Writelog 'I' 1000 $true $true "Features Loaded"
return $Features # $Features has value as expected (1)
}
function GetUserFeatures ($pUserObject)
{
$SfBFeatures = ""
$SfBFeatures = getFeatures #SfBFeaures has Value of $msg and $Features (2)
...
}
Do I use the functions/return values wrong? What could lead to such behavior? Is it an issue if i call a function within a function?
If I remove $msgOut = $date + ... + $msg in writeMessageLog, the values are fine.
I'm pretty lost right now, and have no ideas where this comes from. Any ideas welcome.
This is how powershell works, basically everything that you print out will be returned as the function output. So don't output extra stuff. To force something to not output stuff you can do:
$null = some-command_that_outputs_unwanted_things
since everybody is obsessed with Out-Null I'll add this link showing several other ways to do that.
Within a function, everything you don't assign or pipe to a consuming cmdlet will get put to the pipeline and returned from the function - even if you don't explicit return it. In fact the return keyword doesn't do anything in PowerShell so the following is equivalent:
function Test-Func
{
"Hello World"
}
function Test-Func
{
return "Hello World"
}
So it looks like your writeMessageLog puts anything on the pipeline thus you have to either assign the value to anything:
$notUsed = writeMessageLog($msg)
or (prefered) pipe it to the Out-Null cmdlet:
writeMessageLog($msg) | Out-Null

How to pass arguments to functions in PowerShell

I have the below PowerShell script
Function Publish
{
Param(
[parameter(Mandatory=$true)]
[String]
$RELEASEDIR,
[parameter(Mandatory=$true)]
[String]
$SERVICENAME,
[parameter(Mandatory=$true)]
[String]
$SERVER
)
Get-ChildItem "$RELEASEDIR\*"
$service = Get-Service -Name $SERVICENAME -Computername $SERVER -ErrorAction SilentlyContinue
$service.Status
}
Publish
How I am executing this:
PS C:\Release\RPCPS> .\RPCPublish.ps1 -RELEASEDIR "C:\Location" -SERVICENAME "value" -SERVER "server"
cmdlet Publish at command pipeline position 1
Supply values for the following parameters:
RELEASEDIR:
Even after passing arguments while executing, the script is expecting it again. What am I doing wrong here?
If you want to execute the script by calling the .ps1 as in your example, there is no need to use a function. Your script should look just like this:
Param(
[parameter(Mandatory=$true)]
[String]
$RELEASEDIR,
[parameter(Mandatory=$true)]
[String]
$SERVICENAME,
[parameter(Mandatory=$true)]
[String]
$SERVER
)
Get-ChildItem "$RELEASEDIR\*"
$service = Get-Service -Name $SERVICENAME -Computername $SERVER -ErrorAction SilentlyContinue
$service.Status
The parameters are passed directly to the script and can be used there.
If, on the other hand, you want to establish a (reusable) function, remove just the last line from your script, which calls the function without parameters (which is why it asks for the mandatory parameters every time).
If you remove the last line, you can call the script without parameters once. After that you have a new function Publish in your current session, which you can then call with
Publish -RELEASEDIR "C:\Release\Batchfile" -SERVICENAME "AmazonSSMAgent" -SERVER "10.0.1.91"
independent of the script file.
Your script is creating a function, "Publish", (lines 1-17) and then calling it with no parameters (line 18). Since you've defined parameters as mandatory (lines 4, 7, 10), failing to supply the parameters when you call the function (line 18) causes PowerShell to request values for the unsupplied parameters.
Supplying parameters to the script file itself does not help; there is no mechanism for "automagically" passing those parameters to anything within the script (you would have to explicitly code the script for that).
As Matt suggested in the comments, dot-source your script after deleting line 18, and then call your function explicitly, passing the parameters (publish -RELEASEDIR "C:\Release\Batchfile" -SERVICENAME "AmazonSSMAgent" -SERVER "10.0.1.91").
As per my understanding your requirement is to run the function, and you have to compile the scripts also in Jenkins.
You can do something like this:
Let's say your script name is RPCPublish.ps1 and the path is D:\Folder.
And I can see your function name is Publish.
So in your case,
powershell -command "& { D:\folder\RPCPublish.ps1; Publish }"
You can pass the parameters after this in the script block.
I used a PowerShell plugin (PowerShell) and executed the same.
. "C:\Release\RPCPS\RPCPublish.ps1"
FUunctionName -RELEASEDIR "C:\bin\Release" -SERVICENAME "Service" -SERVER "$env:SERVER" -DISPLAYNAME "Services Air" -BINPATH "D:\Build\Project.exe" -DESCRIPTION "This service hosts Air service" -DESTINATION "d$\Build\"

Correct Way To Request Parameters In Powershell

I'm trying to figure out the correct way to request parameters in a PowerShell script without using a function. With the following sample script I get an error if I don't include the Param within the function.
#Add SharePoint PowerShell SnapIn if not already added
if ((Get-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue) -eq $null) {
Add-PSSnapin "Microsoft.SharePoint.PowerShell"
}
function SomeFunctionName
{
Param(
[Parameter(Mandatory=$true)]
[string]$CollectionUrl,
[Parameter(Mandatory=$true)]
[string]$SourceList,
[Parameter(Mandatory=$true)]
[string]$DestList,
[Parameter(Mandatory=$true)]
[string]$ExpireDays
) # END PARAMS
#DO SOMETHING WITH THE PARAMETERS
}
If I remove the "function" and surrounding brackets just try to request parameters directly in the script I get the following error:
Missing closing ')' in expression.
You need to put the param(...) block at the top of the script before the If/Add-PSSnapin. You can have comments before the param but no other script.

PowerShell function return type not as expected

I have a script that accepts a string parameter :
script-that-takes-string-param.ps1
param(
[Parameter(Mandatory=$true, HelpMessage="path")]
[string]$path,
)
And I have another script that calls the first script :
parent-script.ps1
function CreateDir($dir) {
if (!(Test-Path $dir)) {
mkdir $dir
}
}
function CreatePath($BaseDir, $Environment, $Site, $Domain){
$path = [string]::format("{0}{1}\{2}\{3}", $BaseDir, $Environment, $Site, $Domain)
CreateDir $path
$path
}
$path = CreatePath 'c:\web\' 'qa' 'site1' 'com'
.\script-that-takes-string-param.ps1 -path $path
Running this script throws the exception :
"Cannot process argument transformation on parameter 'path'. Cannot convert value to type System.String"
Casting the parameter doesn't work :
.\script-that-takes-string-param.ps1 -path [string] $path
And casting the function result doesn't work either :
$path = [string] CreatePath 'global' 'site1'
But what is really strange is that if I run parent-script.ps1 twice from the PS command line, the 1st time it throws exceptions, but the 2nd time it executes with no errors.
My best guess would be that your
#do some other stuff with $path
writes something to the standard output, causing the function to return an array that contains said output and the path you expect. Can you send details on what you do in that bit?
Try removing "return". Output that is not saved to a variable is automatically returned. It shouldn't do any difference but it won't hurt to try.
Can you provide a full exception? Without seing the complete exception I get the feeling that the error is caused by something inside your script(ex. a function).
EDIT Your mkdir is causing the problem. When you run it, it returns an object representing the created directory(a DirectoryInfo object if I remember correctly). To fix this, try:
function CreateDir($dir) {
if (!(Test-Path $dir)) {
mkdir $dir | out-null
}
}
or combine them like:
function CreatePath($BaseDir, $Environment, $Site, $Domain){
$path = [string]::format("{0}{1}\{2}\{3}", $BaseDir, $Environment, $Site, $Domain)
if(!(Test-Path $path -PathType Container)) {
New-Item $path -ItemType Directory | Out-Null
}
$path
}