Print debug messages to console from a PowerShell function that returns - debugging

Is there a way to print debug messages to the console from a PowerShell function that returns a value?
Example:
function A
{
$output = 0
# Start of awesome algorithm
WriteDebug # Magic function that prints debug messages to the console
#...
# End of awesome algorithm
return $output
}
# Script body
$result = A
Write-Output "Result=" $result
Is there a PowerShell function that fits this description?
I'm aware of Write-Output and Write-*, but in all my tests using any of those functions inside a function like the one above will not write any debug messages. I'm also aware that just calling the function without using the returned value will indeed cause the function to write debug messages.

Sure, use the Write-Debug cmdlet to do so. Note that by default you will not see debug output. In order to see the debug output, set $DebugPreference to Continue (instead of SilentlyContinue). For simple functions I will usually do something like this:
function A ([switch]$Debug) {
if ($Debug) { $DebugPreference = 'Continue' }
Write-Debug "Debug message about something"
# Generate output
"Output something from function"
}
Note that I would not recommend using the form return $output. Functions output anything that isn't captured by a variable, redirected to a file (or Out-Null) or cast to [void]. If you need to return early from a function then by all means use return.
For advanced functions you can get debug functionality a bit more easily because PowerShell provides the ubiquitous parameters for you, including -Debug:
function A {
[CmdletBinding()]
param()
End {
$pscmdlet.WriteDebug("Debug message")
"Output something from cmdlet"
}
}
FYI, it's the [CmdletBinding()] attribute on the param() statement is what makes this an advanced function.
Also don't forget about Write-Verbose and $pscmdlet.WriteVerbose() if you just want a way to output additional information that isn't debug related.

Related

Powershell problem with service status searching

Hi guys its maybe a easy question for you but im newbie from powershell so can you pls help me?
In school I got an assignment where I needed to make a menu, a script that could search from service to status, and a script that could search from status to service, and it looks like this:
elseif ($menu -eq "2") {
$statusbank = (Get-Service).Status
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if ($statusbank.Contains([string]$sstatuss)) {
$Information = (Get-Service | Where-Object {$_status -eq $sstatuss}).Name | format-list -property Name
Write-Host $Information
}
}
i really dont understand where my problem is.
It dosn't work: It doesn't do anything and then just ends the script
If i debug, i only see it will skip this, even they are a lot of true value in $statusbank :
if ($statusbank.Contains([string]$sstatuss)) {
Try using this instead:
elseif ($menu -eq "2")
{
$statusbank = Get-Service
$sstatuss = Read-Host "Bitte geben Sie ein Status ein z.B Running/Stopped"
if($sstatuss -match '^(Running|Stopped)$' -and $sstatuss -in $statusbank.Status)
{
$statusbank | Where-Object Status -EQ $sstatuss |
Format-Table -Property Name,Status
}
}
To complement Santiago Squarzon's helpful answer with an optimization:
# Prompt until a valid service status identifier is entered.
do {
try {
[System.ServiceProcess.ServiceControllerStatus] $sStatus =
Read-Host "Please specify the desired service status (e.g., Running or Stopped)"
break # A valid value was entered, exit the loop
} catch { }
Write-Warning "Unknown status; please specify one of: $([Enum]::GetNames([System.ServiceProcess.ServiceControllerStatus]))"
} while ($true)
# Now output the names of all services that are in the specified state, if any:
(Get-Service | Where-Object Status -eq $sStatus).Name
Casting the user input (which is always a string) to type [System.ServiceProcess.ServiceControllerStatus] (the type of the .Status property of the objects returned by Get-Service) is used to ensure that a valid service-status identifier was entered.
As for what you tried:
Leaving the inefficiency of calling Get-Service twice aside, your primary problem was the use of the .Contains() .NET array method (implemented via the IList interface):
.Contains() performs no on-demand type conversions, so looking for a string ($sstatuss) in your array of [System.ServiceProcess.ServiceControllerStatus] values ($statusbank) never succeeds.
By contrast, PowerShell's -contains operator does perform on-demand type conversions (as PowerShell generally does) and is notably also case-insensitive (as PowerShell generally is). The same applies to functionally equivalent, but operands-reversed -in operator.
To illustrate the difference:
# Sample array with [System.ServiceProcess.ServiceControllerStatus] elements.
$array = [System.ServiceProcess.ServiceControllerStatus]::Running,
[System.ServiceProcess.ServiceControllerStatus]::Stopped
# WRONG.
$array.Contains('running') # !! always $false with a [string] as input
# OK.
$array -contains 'running' # -> $true - on-demand type conversion
# from string to [System.ServiceProcess.ServiceControllerStatus]
In a nutshell: -contains is in effect using the -eq operator against each element behind the scenes, so the latter's automatic type conversions and case-insensitivity apply. See the bottom section of this answer for more information about -contains and -in.
Pitfall: Due to having the same name, there's potential for confusion with the .Contains() string method, which functions differently, however: it performs literal substring matching, and there is no direct operator equivalent in PowerShell for that - see this answer.
Also:
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer. In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
Write-Host is typically the wrong tool to use, unless the intent is to write to the display only, bypassing the success output stream and with it the ability to send output to other commands, capture it in a variable, or redirect it to a file. To output a value, use it by itself; e.g., $value instead of Write-Host $value (or use Write-Output $value, though that is rarely needed); see this answer

End a function from a sourced script without exiting script

I have a script with many other scripts sourced. Each script sourced have a function.
One of my function need to stop according to a if statement. I tried continue, break, throw, evrything I saw on internet but either my main script completely end or my function continue.
Function code:
function AutoIndus-DeployUser($path){
$yaml=Get-Content $path | ?{!$_.StartsWith("#")}
$UsersPwd=$false
$user="";$password="";$groups="";$description=""; $options=""
$yaml | %{
if (($_-match "}") -and ($UsersPwd -eq $true)){Write-Host "function should stop"; return}
*actions*
}
}
Main script:
#functions list and link to extern scripts
Get-ChildItem -Path $PSScriptRoot\functions\ | %{$script=$_.Name; . $PSScriptRoot\functions\$script}
myfunction-doesnotwork
*some code*
Edit: I saw that return should stop the function without stoping the rest of the script, unfortunatly it does not stop anything at all:
Output:
Config file found
---
Changing user _usr-dba password
Changing user _usr-dba2 password
function should stop
Changing user _usr-dba3 password
function should stop
function should stop
function should stop
function should stop
function should stop
Disabling user DisableAccounts:{
Disable-LocalUser : User DisableAccounts:{ was not found.
At C:\Scripts\AWL-MS-AUTOMATION_AND_TOOLS\functions\AutoIndus-DisableAccount.ps1:25 char:13
+ Disable-LocalUser -Name $disable
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (DisableAccounts:{:String) [Disable-LocalUser], UserNotFoundException
+ FullyQualifiedErrorId : UserNotFound,Microsoft.PowerShell.Commands.DisableLocalUserCommand
Disabling user _usr-dba
To help you understanding, my script read a file and do actions according to each line it is reading. It start acting when it encounter something like User{ and should stop when it encounter }, _usr-dba3 being out of the curvy brackets it should not be treated. This way all my other function use the same one file. (changing user password and diasabling user are two different functions/sourced scripts)
Like you can see return does not do its job, maybe I'm missing one point in the use of return but I don't get it right now.
To prematurely exit out of a function before you reach the end of it, use
return
it works like break to where it will stop execution but instead of stopping the whole thread it will return back to the line below where you called the function.
Also keep in mind if your function has an output set return will return it back, for example with the function below:
function Get-ReturnValue()
{
$returnValue = "this is my output"
return $returnValue
}
if you called it like this:
$receivedReturnValue = Get-ReturnValue
then the variable receivedReturnValue would be loaded with the output of the function.
I needed a fast solution so I changed my functions, now there is no return/continue/... but a value that turn true. It is initialized to false and the action on each yaml lines are done when the value is equal to false.
Edit: So finally the issue was I didn't used function correctly. Return only works if you return into another function. So I putted almost the entire sctipt into a function that calls the function AutoIndus-DeployUser. Now it works perfectly !

How to run sdelete with a whatif flag

I'm writing a PowerShell script finding directories with Get-ChildItem and passing them to sdelete. I would like to trial run it. I don't think sdelete has a -WhatIf flag. Is there any way I can achieve something similar?
You can implement the -WhatIf and -Confirm functionality by adding SupportShouldProcess to the CmdletBinding statement at the top of your function and a if ($pscmdlet.ShouldProcess(something) { } block to your code.
For example:
Function Remove-WithSDelete {
[CmdletBinding(SupportsShouldProcess,ConfirmImpact="High")]
Param ($File)
Process {
if ($pscmdlet.ShouldProcess($File)) {
& sdelete.exe $File
}
}
}
Usage:
PS C:\> Remove-WithSDelete c:\temp -WhatIf
What if: Performing the operation "Remove-WithSDelete" on target "c:\temp".
The ConfirmImpact part is optional and is used to define the level at which -Confirm is invoked automatically. If the impact level of the function is equal to or greater than your $ConfirmPreference level, -Confirm will be the default behaviour (without explicitly invoking it. You can force it off by doing -Confirm:$false). If you omit it your function Confirm Level is "Medium" by default. The default level of the $ConfirmPreference variable is "High".

Powershell Auto-Complete on ValidationSet

Creating a Validation Set is easy.
param(
[Parameter(Mandatory=$true)]
[ValidateSet('Ding','Dong')]
[string]$bellState,
[Parameter(Mandatory=$true)]
[ValidateSet('Dead','Alive')]
[string]$witchesState
)
It provides free auto completion if your Powershell version is >2
However it's not so helpful if you don't pass in the params at the start.
cmdlet Untitled2.ps1 at command pipeline position 1
Supply values for the following parameters:
bellState: Dib
witchesState: Alive
C:\Users\cac\Untitled2.ps1 : Cannot validate argument on parameter 'bellState'. The argument "Dib" does not belong to the set "Ding,Dong" specified by the ValidateSet attribute. Supply an argument that is in the set and then
try the command again.
+ CategoryInfo : InvalidData: (:) [Untitled2.ps1], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Untitled2.ps1
This time there is no tab completion or clues :(
If you type in something invalid you do get a useful error:
"The argument "Dib" does not belong to the set "Ding,Dong""
However this error is thrown at the end of the params not at the time of the original mistake and there isn't an option to try again.
Has anyone found a way of extending this validation to be any more user friendly in the instance it initiated without passed in parameters.
While it might not be exactly what you wanted I would think a simple addition to the script would be to add HelpMessages to your parameters. That way the user has the option to get more information about what they are about to type.
param(
[Parameter(Mandatory=$true,
HelpMessage="You need to pick ding or dong silly")]
[ValidateSet('Ding','Dong')]
[string]$bellState,
[Parameter(Mandatory=$true)]
[ValidateSet('Dead','Alive')]
[string]$witchesState
)
So when called without specifying parameters...
Supply values for the following parameters:
(Type !? for Help.)
bellState: !?
You need to pick ding or dong silly
bellState:
If I remove the mandatory bit and add some code after parameter block I can get the result I want. I understand this might make a pretty useless Cmdlet for piping etc. so I see why it isn't default behavior.
I'm getting around this by having a function for doing the actual work with mandatory params and having a separate helper function to get user input.
It's not so long winded or tricky to get the following to do pretty much whatever you want.
There's an article here
if(!($bellState)){
$title = "Pick a Bell State"
$message = "You need to pick ding or dong silly"
$Ding = New-Object System.Management.Automation.Host.ChoiceDescription "Ding", `
"First Strike."
$Dong = New-Object System.Management.Automation.Host.ChoiceDescription "Dong", `
"Second Strike."
$options = [System.Management.Automation.Host.ChoiceDescription[]]($Ding, $Dong)
$result = $host.ui.PromptForChoice($title, $message, $options, 0)
switch ($result)
{
0 { $type = "Ding" ; "First Strike."}
1 { $type = "Dong" ; "Second Strike."}
}
}

Output not displayed when assigning the return value of a function to a variable

If I have a very simple script that contains a function that just does the following:
function saySomething() {
Write-Output "Saying someting..."
}
If I call this function from the console the output is displayed. If I, however, do the following:
$something = saySomething
Then no output is displayed. Obviously my working example is much more complicated than this, but in short, how do I display the output within a function if that function is used to set a variable?
Try the following:
saySomething | Tee-Object -Variable something
This should work exactly as you want.

Resources