Powershell to start/stop with arg - powershell

I've written the following function in test.ps1 and I would like to make a choise when running thsi script to start/stop/.. :
function getState($SeviceName) {
$server = #('host_1', 'host_2')
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
I would like to provide $ServiceName as argument (with stdin) how can I do it? => somthing like choose 1 to start 2 to stop ...
To use switch/case in Powershell
$doAction = {"Stop-Service", "Start-service"}
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName | $doAction}
How do I use the switch to select start or stop?

Here's a function that will do what you're asking for:
function Get-State {
[CmdletBinding()]
[OutputType('System.ServiceProcess.ServiceController')]
param(
[Parameter(Position = 0, Mandatory)]
[ValidateSet('Start', 'Stop', 'Get')]
[string] $Action,
[Parameter(Position = 1, ValueFromPipeline, Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $ServiceName
)
begin {
$serverList = #('host_1', 'host_2')
}
process {
foreach ($server in $serverList) {
try {
$svc = Get-Service -ComputerName $server -Name $ServiceName -ErrorAction Stop
} catch {
throw "Failed to find service $ServiceName on $server! $PSItem"
}
switch ($Action) {
'Start' { $svc | Start-Service -PassThru }
'Stop' { $svc | Stop-Service -Force -PassThru }
default { $svc }
}
}
}
}
It utilizes advanced function features and attributes to take pipeline input (stdin in your words). I'd suggest reading this documentation.

You can add argument to a script by adding parameters to it.
On the top of your script file put:
Param
(
[parameter()]
[String[]]$YourArgumentVariable
[parameter()]
[switch] $MySwitch
)
With a function it goes right after the function definition. So in your case:
function getState($SeviceName) {
Param
(
[parameter()]
[String[]]$server
[parameter()]
[switch] $MySwitch
)
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
A switch basically sets a boolean to true or false.
So in this if you call the script with -MySwitch it will set the variable $MySwitch to true. Else it will remain false.
Don Jones has written a good getting started article on paramters that I would recommend you checking out.
Do note that there are loads of things you can define in the paramter. Like if you want to make sure it is always filled you can set
[parameter(Mandatory=$true)]
This is just one of many examples of what you can do with paramters.

Related

Pass schtasks.exe output to foreach loop in PowerShell

I am using powershell5.1 and I am trying to grab a few tasks running on a remote server and see if any are running. IF there are any running then I will have the script sleep for a minute and then check again until all the tasks I am checking are in Ready status.
What I use to query the remote server:
$servers = "Server1"
""
foreach($server in $servers)
{
Invoke-Command -ComputerName $server -ScriptBlock{
schtasks /query /fo list /tn RandomTaskName
}
}
Which then outputs this result:
HostName: $server
TaskName: \RandomTaskName
Next Run Time: 3/1/2022 11:40:00 AM
Status: Running
Logon Mode: Interactive/Background
So I add a pipe to FINDSTR "Status: Running"
schtasks /query /fo list /tn RandomTaskName | FIDNSTR "Status: Running"
That returns just the string.
Then I try to add the output to a variable and do a foreach loop to see if a string contains "Status: Running"
$servers = "Server1"
foreach($server in $servers)
{
Invoke-Command -ComputerName $provider -ScriptBlock{
schtasks /query /fo list /tn RandomTaskName
}
$task = $_
if ("Status: Running" -ccontains $task)
{
Wrtite-host "Task is still running"
}
else
{
Write-Host "Task is NOT running"
}
}
Which results in nothing. When I comment out the "if" and "else" statements so only "$task=$_" is at the end, then it results in "Status: Running".
Anyone can give any insight as to how to grab the running status for a list of remote servers?
When I saw this question, it really peaked my curiosity. I'm working more with workstations than servers, but everything should be about the same.
This is entirely my approach to solve my own problems, so it may not fit your needs - sorry. But perhaps my code can give you some ideas on how to do what you want.
This code:
Has a function called Invoke-InBackgroundOnMachines that takes an array of machines, a list of arguments, and a scriptblock.
Invoke-InBackgroundOnMachines gets pipeline content from each remote machine that the scriptblock is ran on and returns it on the pipeline.
Invoke-InBackgroundOnMachines checks to see if it can connect to each machine before trying to run the scriptblock on it. Machines that it cannot connect to are returned on the pipeline in a comma delimited text of "Machine", "NoConnection", "NoConnection". Machines that it can connect to are stored in the LiveMachines array, which is later handed to Invoke-Command, along with the scriptblock and argument list.
The one thing I haven't figured out yet is how to best deal with computers that do not have WinRM enabled. Invoke-Command throws errors when it hits a computer with WinRM disabled. This is my first attempt at running remote script blocks, so still have a lot to figure out.
The scriptblock itself wound up being a lot larger than I was expecting. But it does the job of returning the status of tasks in a comma delimited format.
This is not properly tested code. I started with cervan12's code this morning and started writing. So keep that in mind.
# Used Example 8 from following URL
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-5.1
$SchTasksScriptBlock = {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$TaskNameToFind
)
function CommaDelimited {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Tokens
)
$Return = ''
foreach ($Token in $Tokens) {
$Return += ", `"$Token`""
}
$Return.Substring(2)
}
$STReturn = SCHTASKS /Query /FO LIST /TN $TaskNameToFind 2>$null
if ($LASTEXITCODE -eq 0) {
$HostName = ''
switch -Regex ($STReturn) {
'(?i)^\s*hostname:\s*(?<HostName>.*)\s*$' {
if ($HostName -ne '') { CommaDelimited $HostName, $TaskName, $Status }
$HostName = $Matches.HostName
continue
}
'(?i)^\s*taskname:\s*(?<TaskName>.*)\s*$' {
$TaskName = $Matches.TaskName
continue
}
'(?i)^\s*status:\s*(?<Status>.*)\s*$' {
$Status = $Matches.Status
continue
}
Default {
continue
}
}
if ($HostName -ne '') { CommaDelimited $HostName, $TaskName, $Status }
} else {
$HostName = HostName
CommaDelimited $HostName, $TaskNameToFind, 'Missing'
}
}
function Invoke-InBackgroundOnMachines {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Machines,
[Parameter(Mandatory = $true, Position = 1)]
[object[]]$ArgumentList,
[Parameter(Mandatory = $true, Position = 2)]
[scriptblock]$ScriptBlock
)
[string[]]$LiveMachines = #()
foreach ($Machine in $Machines) {
if(Test-Connection -ComputerName $Machine -Quiet) {
$LiveMachines += $Machine
} else {
"`"$Machine`", `"NoConnection`",`"NoConnection`""
}
}
If($LiveMachines.Count -gt 0) {
$Session = New-PSSession -ComputerName $LiveMachines
$null = Invoke-Command -Session $Session -ScriptBlock $ScriptBlock -AsJob -ArgumentList $ArgumentList
Get-Job | Wait-Job | Receive-Job
}
}
Example use:
$List = #( 'Machine1', 'Machine1' )
Invoke-InBackgroundOnMachines $List '\AlertusSecureDesktopLauncher' $SchTasksScriptBlock
Example results returned on the Pipeline:
"Machine1", "\AlertusSecureDesktopLauncher", "Ready"
"Machine2", "\AlertusSecureDesktopLauncher", "Ready"

PowerShell implementing -AsJob for a cmdlet

Is there a nice way to implement the switch parameter -AsJob in custom cmdlets, like Invoke-Command has?
The only way I thought about this is:
function Use-AsJob {
[CmdletBinding()]
[OutputType()]
param (
[Parameter(Mandatory = $true)]
[string]
$Message,
[switch]
$AsJob
)
# Wrap Script block in a variable
$myScriptBlock = {
# stuff
}
if ($AsJob) {
Invoke-Command -ScriptBlock $myScriptBlock -AsJob
}
else {
Invoke-Command -ScriptBlock $myScriptBlock
}
}
Is there a better approach? I couldn't find Microsoft docs on this, any lead helps.
If we make the following assumptions:
Command is a script function
Function does not rely on module state
Then you can use the following boilerplate for any command:
function Test-AsJob {
param(
[string]$Parameter = '123',
[switch]$AsJob
)
if ($AsJob) {
# Remove the `-AsJob` parameter, leave everything else as is
[void]$PSBoundParameters.Remove('AsJob')
# Start new job that executes a copy of this function against the remaining parameter args
return Start-Job -ScriptBlock {
param(
[string]$myFunction,
[System.Collections.IDictionary]$argTable
)
$cmd = [scriptblock]::Create($myFunction)
& $cmd #argTable
} -ArgumentList $MyInvocation.MyCommand.Definition,$PSBoundParameters
}
# here is where we execute the actual function
return "Parameter was '$Parameter'"
}
Now you can do either:
PS C:\> Test-AsJob
Parameter was '123'
PS C:\> Test-AsJob -AsJob |Receive-Job -Wait
Parameter was '123'

Pass argument to Workflow (powershell)

I know that you can't use Read-Host within a Workflow. But I need to prompt the user via Read-Host and then use that input data within a Workflow. We need to execute MSG.EXE on remote machines (since NetSend went bye bye)
I have this code. It runs without error but it does Not send any message. how do I pass my argument into the Workflow so it works?
$Computers = #("mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257")
workflow Test-WFConnection {
param(
[Parameter (Mandatory = $true)]
[object[]]$message
)
foreach -parallel ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {msg /SERVER:$computer * $msg}
}
}
$msg = Read-host "Enter your message here"
Test-WFConnection -message $msg
The issue is the $msg never gets sent to any machine.
To deal with workflows you have to understand the scopes in workflow. Here, the value of $msg is not with the scope thats why the value is not at all coming inside the block. So you have to use $Using:msg , then it will be available.
I will give a small example below to clarify your doubt:
workflow sample_test
{
$variable1 = 5
# Changes to variable value in an InlineScript do not affect
# the value of the workflow variable.
InlineScript {$variable1 = $Using:variable1 + 1; "Inline Variable1 = $variable1"}
"Workflow variable1 = $variable1"
# To change the value in workflow scope, return the new value.
$a = InlineScript {$variable1 = $Using:variable+1; $variable1}
"Workflow New variable1 = $variable1"
}
Below is the screenshot for your reference:
Hope it helps...!!!
How about you declare the computers in the workflow?
workflow send-message {
param(
[Parameter (Mandatory = $true)]
[string]$message
)
$computers = "mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257"
foreach -parallel ($computer in $computers){
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue){
msg /SERVER:$computer * $message
}}}
send-message -message (read-host 'Enter message')
This worked ok when I tested it.
As posted here: https://community.spiceworks.com/topic/1951356-pass-argument-to-workflow
you can pass the computers as parameter like this or is there any limitation that stops you from doing that:
$Computers = #("mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257")
workflow Test-WFConnection {
param(
[Parameter (Mandatory = $true)]
[object[]]$message,[object[]]$computers
)
foreach -parallel ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {msg /SERVER:$computer * $msg}
}
}
$msg = Read-host "Enter your message here"
Test-WFConnection -message $msg -computers $Computers
I'm not certain what is happening with these answers - they're a bit obscure.
Neal5's answer was the correct answer, but without actually explaining why (although I would also suggest adding the list of computers as a parameter as in Abhijith pk's answer).
The issue is the statement that is run if the condition is met:
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {
msg /SERVER:$computer * $msg
}
You're using $msg here, however you have named the parameter $message:
param(
[Parameter (Mandatory = $true)]
[object[]]$message
)
So you're statement should read:
msg /SERVER:$computer * $message

How do I pass a hashtable to a scriptblock?

I tried to pass a hashtable to a scriptblock like this where $arg3 is my hashtable. However, it failed. How do I do it in the correct way?
It doesn't seem like it is passing anything to the script block.
$commandParameters.ComputerName = $ComputerName
$commandParameters.ScriptBlock = {
param(
[Parameter()]
[switch]$arg1 = $false,
[Parameter()]
[array]$arg2,
[Parameter()]
[hashtable]$arg3
)
enter code here
Doing something here
}
Invoke-Command #commandParameters -ArgumentList $arg1, #($arg2), $arg3
=================================================================
I found the answer myself and it works for me. This is how I build the associative array and then pass it to the script block.
I am not sure why, but I was using the dot notation ($hash.a.b) to reference the hash table in a function and it works, but it doesn't work for a script block. It looks like I need to use [ ] (e.g. $hash[a][b])in the script block.
$compADGroups = #{}
foreach ( $adGroup in $adGroups ) {
if ( $compADGroups.$computerNameGroup -eq $null ) {
$compADGroups[$computerName] = #{}
$compADGroups[$computerName]["Group"] = #{}
$compADGroups[$computerName]["Group"] = $hashString
}
}
$session = New-PSSession -ComputerName 'Computer1'
Invoke-Command -Session $session -ArgumentList $compADGroups -ScriptBlock { param($compADGroups) $compADGroups[$env:computername]["Group"]}
Get-PSSession | Remove-PSSession
Make sure you're using Invoke-Command correctly.
$ScriptBlock = {
param(
[Parameter(Mandatory=$True, Position=1)]
[hashtable]$myHashTable
)
# Code here
}
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList ([hashtable]$hashtable)
If you specify certain arguments for a scriptblock, make sure you also describe the position value, and more often than not whether it's mandatory. If you are trying to pass your hashtable in as the second argument in an implicitly defined array of arguments, write your scriptblock so that it takes the hashtable at that specific position.
For example,
$ScriptBlock= {
param(
[Parameter(Position=2)] # Take note of the position you set here
[hashtable]$myHashTable,
[Parameter(Position=1)]
[string]$myString,
[Parameter(Position=3)]
[int]$myInteger
)
# Do stuff
}
Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList ($myString, $myHashTable, $myInteger);
# ^ variable is in second position

Powershell v3 Isfilter unnamed script block.

The following code snipit works in PowerShell v2, but not v4.. In the release notes for PowerShell v3 is explains that you cannot set the IsFilter property on an unnamed script block. I believe that's exactly what I have, but I don't understand what change to make..
Any help would be appreciated.
function Stop-WindowsService
{
param(
[Parameter(Mandatory=$true,ValueFromPipeline=$true)]
$fromPipe,
[Parameter(ParameterSetName='static',Mandatory=$true,Position=0)]
[ValidateNotNullOrEmpty()]
[string]$name,
[Parameter(ParameterSetName='dynamic',Mandatory=$true,Position=0)]
[ValidateNotNull()]
[ScriptBlock]$scriptReturningName,
[Parameter(Mandatory=$false)]
[ValidateRange(1,86400)]
[int]$timeout = 60
)
Process {
$server = $_
if($PsCmdlet.ParameterSetName -eq 'dynamic') {
$scriptReturningName.IsFilter = $true
$name = ($server | &$scriptReturningName)
}
Write-Verbose "$($server.Name): $name ==> Checking"
$service = $server | Get-WindowsServiceRaw $name