karesz55
karesz55

Reputation: 27

Run command with PowerShell

I want to make a script that which if I open it on my machine (as administrator), it run a command on different machines (without entering credentials). I used to make a script that restarts services. I did it like this:

$out = "lokation\output_NoService.txt"
Clear-Content $out

$data = Get-Content "lokation\input_PCs.txt"

foreach ($line in $data) {
    $computer = $line.Split(":")[0]
    $ServiceName = $line.Split(":")[1]

    if ($sObj = Get-Service-COmputerName $Computer -Name $ServiceName -ErrorAction SilentlyContinue) {
        Restart-Service -InputObj $sObj
        Write-Host "Restarting $ServiceName on $computer"
    } else {
        Write-Host "$ServiceName is not running on $computer"
        Add-Content $out "$computer;$ServiceName"
    }
}

input_PCs.txt contained the machine names and the name of the service to be restarted. eg: PCName:Servicename

This is how I want to scan PCs from a txt and run command on the machines that are in txt.

For example, such a command.

run example.exe + paramters from c drive. Ergo: C:\example.exe -paramaters

Upvotes: 0

Views: 136

Answers (2)

Theo
Theo

Reputation: 61013

The Restart-Service does not have a -ComputerName parameter to manage services on remote machines. To use that cmdlet remotely, you can use

Invoke-Command -Computername $computer -ArgumentList $ServiceName -ScriptBlock { 
    param($service)
    Restart-Service -Name $service}


Edit


Lee_Daily pointed out that the object returned from Get-Service contains a MachineName property. When this object is used in a following Restart-Service as InputObject parameter, the cmdlet uses that MachineName property to restart the service on that remote machine.

As I have never thought of that, I've been testing with it and indeed it does work that way.

Your code (with corrected ComputerName parameter) should therefore work as intended, provided you have permissions on the remote computers to do so:

$out = "lokation\output_NoService.txt"
Clear-Content $out

$data = Get-Content "lokation\input_PCs.txt"

foreach ($line in $data) {
    $computer, $serviceName = $line -split ':', 2

    if ($sObj = Get-Service -ComputerName $computer -Name $serviceName -ErrorAction SilentlyContinue) {
        Restart-Service -InputObj $sObj
        Write-Host "Restarting $serviceName on $computer"
    } 
    else {
        Write-Host "Service '$serviceName' cannot be found on computer '$computer'"
        Add-Content -Path $out -Value "$computer; $serviceName"
    }
}

This however does not let you add user credentials because Get-Service does not provide a Credentials object. If you need to use different credentials, you can do like below:

$credential = Get-Credential
$out = "lokation\output_NoService.txt"
Clear-Content $out

$data = Get-Content "lokation\input_PCs.txt"

# unravel the $credential object into plain username and password:
$user     = $credential.GetNetworkCredential().UserName
$password = $credential.GetNetworkCredential().Password

foreach ($line in $data) {
    $computer, $serviceName = $line -split ':', 2
    # create a connection to the remote machine using credentials
    net use "\\$computer\c$" $password /USER:$user

    if ($sObj = Get-Service -ComputerName $computer -Name $serviceName -ErrorAction SilentlyContinue) {
        Restart-Service -InputObj $sObj
        Write-Host "Restarting $serviceName on $computer"
    } 
    else {
        Write-Host "Service '$serviceName' cannot be found on computer '$computer'"
        Add-Content -Path $out -Value "$computer; $serviceName"
    }
}

The last option I can think of is to use the Get-WmiObject cmdlet which for convenience I've wrapped in a custom function:

function Restart-RemoteService {
    [CmdletBinding()]
    Param(
       [Parameter(Position = 0,Mandatory = $true, ValueFromPipelineByPropertyName = $true,ValueFromPipeline = $true)]
       [string]$ComputerName, 

       [Parameter(Position = 1,Mandatory = $true)]
       [string]$ServiceName,

       [Parameter(Position = 2,Mandatory = $true)]
       [string]$ErrorFile

       [Parameter(Mandatory = $false)]
       [System.Management.Automation.PSCredential]$Credential
    )

    $wmiParams = @{
        'Class'        = 'Win32_Service'
        'ComputerName' = $ComputerName
        'Filter'       = "Name='$ServiceName'"
    }
    if ($Credential) { $wmiParams['Credential'] = $Credential }

    $svc = Get-WmiObject @wmiParams
    if (!$svc) {
        Write-Warning "Service '$ServiceName' cannot be found on computer '$ComputerName'"
        Add-Content -Path "lokation\output_NoService.txt" -Value "$ComputerName; $ServiceName"
    }
    if ($svc.State -eq 'Running') {
        Write-Verbose "Stopping service '$ServiceName' on computer '$ComputerName'"
        [void]$svc.StopService()
        $maxWait = 20
        do {
            Start-Sleep -Milliseconds 100
            $maxWait--
            if ($maxWait -le 0) {
                Throw "Could not stop service '$ServiceName'"
            }
        } while (( Get-WmiObject @wmiParams).State -ne 'Stopped')

    }
    Write-Verbose "Starting service '$ServiceName' on computer '$ComputerName'"
    [void]$svc.StartService()
}

Your code could then look like this:

$out = "lokation\output_NoService.txt"
Clear-Content $out

$data = Get-Content "lokation\input_PCs.txt"

foreach ($line in $data) {
    $computer, $serviceName = $line -split ':', 2

    # if needed, you can pass Credentials too
    Restart-RemoteService -ComputerName $computer -ServiceName $serviceName -ErrorFile $out -Verbose
}

Upvotes: 2

Michel Balencourt
Michel Balencourt

Reputation: 156

Something like this ?

foreach($line in $data) {

    $computer = $line.split(":")[0]   

    Invoke-Command -ComputerName $computer -ScriptBlock {C:\example.exe -parameters}

}

In order for this to work, example.exe will need to be present on your remote computers. If this isn't the case yet, before the above, you can add something like this

if (!(Test-Path -Path "\\$computer\c$\example.exe")) {

    Copy-Item -Path "X:\path_to_file\example.exe" -Destination "\\$computer\c$" -Force

}

And if you don't want it to request any credentials, make sure your Powershell session is started under your admin user that has access to all the computers' c-drives.

Upvotes: 0

Related Questions