PowerShell Functions and varying parameters - function

Early on in my script I have check to define whether a parameter "-Silent" was used when running the script. The idea is to have zero output from the script if it was and it will be checked on every Write-Host entry I have later. It seems a bit heavy to make if-else statements on every single Write-Host I have, so I decided to go with a function - something like this:
Function Silent-Write ([string]$arg1)
{
if ($silent -eq $false) {
if ($args -ieq "-nonewline") {
Write-Host "$arg1" -NoNewLine
}
elseif ($args -ieq "-foregroundcolor") {
Write-Host "$arg1" -ForegroundColor $args
}
else {
Write-Host "$arg1"
}
}
}
Silent-Write -ForegroundColor red "hello"
This is not working, but you get the idea; besides passing the text I want to output, Silent-Write function should also take other Write-Host arguments into consideration. Quite simple issue I believe, but something I cannot figure out with the knowledge of functions I have.

In PowerShell V3 you can use splatting:
Function Silent-Write
{
if (!$silent) {
Write-Host #args
}
}
Silent-Write -ForegroundColor red "hello"

Related

Powershell Function Switch

I'm trying to create a quasi-logging function and pass the function parameter as a variable for possible output.
Function Get-Function($continue) {
if (!$error) {
Write-Host "pass"
} else {
$continue
}
}
Get-Function -continue $("$(write-host)success")
If there is an error it outputs success which is listed after the -continue flag.
But this version errors due to the pipeline:
Get-Function -continue $("$(Write-Host)success") | Write-Host "this fails"
It creates an error after the pipeline.
I'm a bit confused as to the question, but it's failing because you're trying to pipe information into a command which you already are giving the parameters. For example:
Works:
"Success" | Write-Host
Write-Host "Success"
Fails:
"Success" | write-host "Success"
If you change that line to just Get-Function -continue "$(Write-Host)success" | Write-Host, it will work but is pointless. You have Write-Host in your function, so there's really no point to write it again. Hope that helps!

Running multiple functions in a foreach loop

I have a large Powershell script that checks multiple variables on VMs. The script consists of about 80 different functions that are named question1, question2, question3...
At first none of the functions needed parameters, so this code worked.
$number_of_questions = 1..75
foreach($num in $number_of_questions){
Invoke-Expression question$num
}
It iterates thru every question
But now i need to add parameters for when i run the functions. And that doesn't work. And i cant find a way to get it to work with arguments
HereĀ“s a testversion of what im trying to do.
function test1($text){
Write-host "Not argument"
Write-host $text
}
function test2($text){
Write-host "Not argument"
Write-host $text
}
function test3($text){
Write-host "Not argument"
Write-host $text
}
function test4($text){
Write-host "Not argument"
Write-host $text
}
function test5($text){
Write-host "Not argument"
Write-host $text
}
$num = 1..5
foreach($number in $num){
Invoke-Expression test$number -text "Argument"
}
Does anyone have a solution for running multiple functions with sequenced names that uses parameters.
Just replace:
Invoke-Expression test$number -text "Argument"
with:
Invoke-Expression "test$number -text `"Argument`""
to make it work.

Exit a PowerShell function but continue the script

This might seem like a very very stupid question, but I can't really figure it out. I'm trying to have the function stop when it finds its first hit (match) and then continue with the rest of the script.
Code:
Function Get-Foo {
[CmdLetBinding()]
Param ()
1..6 | ForEach-Object {
Write-Verbose $_
if ($_ -eq 3) {
Write-Output 'We found it'
# break : Stops the execution of the function but doesn't execute the rest of the script
# exit : Same as break
# continue : Same as break
# return : Executes the complete loop and the rest of the script
}
elseif ($_ -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
Desired result:
VERBOSE: 1
VERBOSE: 2
VERBOSE: 3
We found it
The script continues here
I've tried using break, exit, continue and return but none of these get me the desired result. Thank you for your help.
As was mentioned, Foreach-object is a function of its own. Use regular foreach
Function Get-Foo {
[CmdLetBinding()]
Param ()
$a = 1..6
foreach($b in $a)
{
Write-Verbose $b
if ($b -eq 3) {
Write-Output 'We found it'
break
}
elseif ($b -eq 5) {
Write-Output 'We found it'
}
}
}
Get-Foo -Verbose
Write-Output 'The script continues here'
The scriptblock you are passing to ForEach-Object is a function in its own right. A return in that script block just returns from the current iteration of the scriptblock.
You'll need a flag to tell future iterations to return immediately. Something like:
$done = $false;
1..6 | ForEach-Object {
if ($done) { return; }
if (condition) {
# We're done!
$done = $true;
}
}
Rather than this, you may be better using a Where-Object to filter the pipeline objects to only those that you need to process.

Passing a cmdlet as a parameter

I need to restart a service in a powershell script. The problem is that this service is a bit buggy and frequently needs to be shut down several times before it gets into the "stopped" state. Because of that I can't seem to use the Restart-Service cmdlet, instead I need to retry the Stop-Service cmdlet a few times. The same applies to starting the service.
So I figure this is a good place to write a function that will take an action (start or stop) and retry it a few times until it works. The problem is I'm not sure how to pass the action in as a parameter. I could just have the action be a String and then say if action == "start" do starcAction, but that won't be very clean. Is there any way I could pass a cmdlet like Stop-Service in as a parameter?
For the scenario you described you'd normally do something like this:
$maxTries = 5
switch ($args(0)) {
'start' {
Start-Service 'MySvc'
}
'stop' {
$i = 0
do {
Stop-Service 'MySvc'
$i++
} until ((Get-Service 'MySvc').Status -eq 'Stopped' -or $i -ge $maxTries)
if ((Get-Service 'MySvc').Status -ne 'Stopped') {
Write-Error "Cannot stop service."
exit 1
}
}
default {
Write-Error "Unknown action: $_"
exit 1
}
}
If you really want to avoid string arguments, you could use parameter sets like this:
[CmdletBinding(DefaultParameterSetName='none')]
Param(
[Parameter(Mandatory=$true,ParameterSetName='start')]
[Switch][bool]$Start = $false,
[Parameter(Mandatory=$true,ParameterSetName='stop')]
[Switch][bool]$Stop = $false
)
$maxTries = 5
switch ($PSCmdlet.ParameterSetName) {
'start' {
Start-Service 'MySvc'
}
'stop' {
$i = 0
do {
Stop-Service 'MySvc'
$i++
} until ((Get-Service 'MySvc').Status -eq 'Stopped' -or $i -ge $maxTries)
if ((Get-Service 'MySvc').Status -ne 'Stopped') {
Write-Error "Cannot stop service."
exit 1
}
}
'none' {
Write-Error "Usage: $($MyInvocation.MyCommand.Name) {-Start|-Stop}"
exit 1
}
}
Param([Parameter(Mandatory)] [ValidateSet('Start','Stop')] [string] $Action)
This allows the user to press Tab to select the possible values and will automatically reject all invalid input.
Passing in a well defined parameter (doesn't matter if it's a string or not) is actually cleaner than "passing in a commandlet" would be if there was such a thing.

Create a function with optional call variables

Is there a way to create a parameter in a PowerShell function where you have to call it in order to have it considered?
An example given by commandlet (the bold being what I want to do):
Invoke-Command -computername Server01 -Scriptblock {...}
Here is an example of what I want to do with the function
Function DoStuff($computername, -arg2, -domain $domain)
Test-parameter(-domain) if (-domain -eq $true) {
use $domain
}
Else {
$domain = "Domain1"
}
test-parameter($arg2) {
if ($arg2 -eq $true) {
Do something
}
else {
Do the opposite
}
}
So in summary:
If "-arg2" is present, I want something to happen in the script. If "-Domain" is present and has an argument with it, I want that to be used rather then the set argument.
Powershell provides a lot of built-in support for common parameter scenarios, including mandatory parameters, optional parameters, "switch" (aka flag) parameters, and "parameter sets."
By default, all parameters are optional. The most basic approach is to simply check each one for $null, then implement whatever logic you want from there. This is basically what you have already shown in your sample code.
If you want to learn about all of the special support that Powershell can give you, check out these links:
about_Functions
about_Functions_Advanced
about_Functions_Advanced_Parameters
I don't think your question is very clear, this code assumes that if you're going to include the -domain parameter, it's always 'named' (i.e. dostuff computername arg2 -domain domain); this also makes the computername parameter mandatory.
Function DoStuff(){
param(
[Parameter(Mandatory=$true)][string]$computername,
[Parameter(Mandatory=$false)][string]$arg2,
[Parameter(Mandatory=$false)][string]$domain
)
if(!($domain)){
$domain = 'domain1'
}
write-host $domain
if($arg2){
write-host "arg2 present... executing script block"
}
else{
write-host "arg2 missing... exiting or whatever"
}
}
Not sure I understand the question correctly.
From what I gather, you want to be able to assign a value to Domain if it is null and also what to check if $args2 is supplied and according to the value, execute a certain code?
I changed the code to reassemble the assumptions made above.
Function DoStuff($computername, $arg2, $domain)
{
if($domain -ne $null)
{
$domain = "Domain1"
}
if($arg2 -eq $null)
{
}
else
{
}
}
DoStuff -computername "Test" -arg2 "" -domain "Domain2"
DoStuff -computername "Test" -arg2 "Test" -domain ""
DoStuff -computername "Test" -domain "Domain2"
DoStuff -computername "Test" -arg2 "Domain2"
Did that help?