Invoke DOS Command in PowerShell

Unfortunately, there are still legacy dos applications that still provide value for Windows administrators.  Also, PowerShell isn’t yet installed on all our Windows servers and can’t be soley relied on:(  So being able to invoke a remote DOS command from PowerShell can be helpful at times.  I’ve written a script to do just that.  It works by creating a process on the remote system using WMI and writing the results to a temporary text file.  The function will then grab the results from the temp file on the remote system and return them.

Here I’m pulling netstat data from a remote system

5

Or maybe I don’t want to get anything back, but I want to force Windows Update to check for new updates

6

Function Code:

Invoke-DOSCommand

function Invoke-DOSCommand {
<#
.SYNOPSIS
Run a DOS command on a remote host and return results
.DESCRIPTION
Invoke-DOSCommand will run a dos command on a remote system and will return the results.
It does this using WMI to create a process and by creating a temporary file on the remote
to store the results if any
.EXAMPLE
Invoke-DOSCommand -Command "netstat -ano"

DESCRIPTION
-----------
This command will return the results from netstat on the local system
.EXAMPLE
Invoke-DOSCommand -Command "netstat -ano" -ComputerName "SERVER1"

DESCRIPTION
-----------
This command will return the results from netstat on the remote system SERVER1
.EXAMPLE
Invoke-DOSCommand -Command "netstat -ano" -ComputerName "SERVER1" | ?{$_ -match "LISTENING"}

DESCRIPTION
-----------
This command is the same as the previous example, but here we are returning only interfaces
that are in the listening state
#>
    [CmdletBinding()] param(                
        [Parameter(Mandatory=$true)] $Command,
        [Parameter(Mandatory=$false)] $ComputerName,
        [Parameter(Mandatory=$false)] [string]$TempLocalDirPath="C:\",
        [Parameter(Mandatory=$false)] [switch]$Force
    )
    
    process{
        
        #if computername is specified assume remote system
        if($ComputerName){
        
            $tempFileName = (get-date -UFormat "%Y%m%d%H%M%S") + "DOStemp.txt"
            
            #generate local file path
            if( $TempLocalDirPath.EndsWith('\') ) {
                $localFilePath = $TempLocalDirPath + $tempFileName
            }
            else {
                $localFilePath = $TempLocalDirPath + '\' + $tempFileName
            }
            
            #generate remote file path from local path using hidden admin share
            try {
                $remoteFolderPath = "\\$ComputerName\" + (Split-Path $TempLocalDirPath -Qualifier -ErrorAction Stop).TrimEnd(":") + "$" + (Split-Path $TempLocalDirPath -NoQualifier -ErrorAction Stop)
            }
            catch {
                Write-Host "`nERROR: Bad TempLocalDirPath value" -ForegroundColor Magenta
                Write-Host "`n" $_.Exception.Message "`n" -ForegroundColor Magenta
            }
            
            #test that folder exists and is reachable
            if( !(Test-Path $remoteFolderPath) ) {
                Write-Host "`nERROR: Remote path $remoteFolderPath desn't exist or is inaccessible`n" -ForegroundColor Magenta
                return
            }
            
            #add filename to path
            if( $remoteFolderPath.EndsWith('\') ) {
                $remoteFilePath = $remoteFolderPath + $tempFileName    
            }
            else {
                $remoteFilePath = $remoteFolderPath + '\' + $tempFileName
            }
            Write-Host "`nTemp file will be located at $remoteFilePath" -ForegroundColor Cyan

            #if there is already a file with this name -Force required
            if( (Test-Path $remoteFilePath) -and !($Force) ) {
                Write-Host "`nWARNING: File $remoteFilePath Already Exists" -ForegroundColor Yellow
                Write-Host "You can use the -Force parameter to overwrite or you can" -ForegroundColor Yellow
                Write-Host "specify a different temp path using -TempLocalDirPath" -ForegroundColor Yellow
                Write-Host "the default path is C:\`n" -ForegroundColor Yellow
                return
            }

            #create remote dos command
            $cmd = "cmd /c $Command > $localFilePath"
        
            #run command as process using WMI invocation
            try {
                $processID = (Invoke-WmiMethod -Class win32_process -Name create -ArgumentList $cmd -ComputerName $ComputerName -ErrorAction Stop).ProcessID
            }
            catch {
                Write-Host "`nERROR: creating process" -ForegroundColor Magenta
                Write-Host "`n$_`n" -ForegroundColor Magenta
                return
            }
            
            #wait for process to complete
            Write-Host "`nWaiting for process $processID to complete..." -ForegroundColor Cyan -NoNewline
            while (Get-Process -Id $processID -ComputerName $ComputerName -ErrorAction SilentlyContinue) {
               Start-Sleep -Seconds 2
               Write-Host "." -ForegroundColor Cyan -NoNewline
            }
            Write-Host ""
        
            #get results from temp file
            Start-Sleep -Seconds 4
            try {
                $result = Get-Content $remoteFilePath -ErrorAction Stop
            }
            catch {
                Write-Host "`nERROR: Couldn't get file $remoteFilePath" -ForegroundColor Magenta
                Write-Host "`n" $_.Exception.Message "`n" -ForegroundColor Magenta
                return
            }
        
            #cleanup temp file
            Remove-Item $remoteFilePath -Force -ErrorAction SilentlyContinue
            if( Test-Path $remoteFilePath ) {
                Write-Host "`nWARNING: Unable to remove remote file" -ForegroundColor Yellow
            }
        }
        #otherwise run command on local system
        else {
            $result = cmd /c $Command
        }
        
        #return command results
        if($result -eq $null) {
            Write-Host "`nNothing was returned by remote command`n" -ForegroundColor Cyan
            return
        }
        else {
            return $result
        }
    }
}

Invoke Remote SSH Command From PowerShell

I find that running scripts against both Windows and Linux systems together can be more efficient than running separate scripts.  This can be accomplished quite easily with the plink.exe binary from the PuTTY installation package.

The following script is designed to work with the default installation path of plink.exe (“C:\Program Files (x86)\PuTTY\plink.exe”), but you can specify an alternate path for the executable if you wish.  You need to specify a “ComputerName” as well as the remote SSH “Command” you wish to run.  If you are going to bulk run against many systems, you can pass the “UserName”, “SecurePassword”, and even “AutoAcceptKeys” to ignore the SSH key warning.

Here I’m grabbing some uname information from a remote Linux system

7

Here I’m getting the RedHat release info. This time I’m passing the “UserName” and “SecurePassword” so I don’t get prompted

8

Function Code:

Invoke-SSHCommand

function Invoke-SSHCommand {
<#
.SYNOPSIS
Invoke SSH Command on a Remote Computer
.DESCRIPTION
Invoke-SSHCommand uses PuTTY's plink.exe to execute a SSH command on a remote system
.EXAMPLE
Invoke-SSHCommand -Command "uname -a" -ComputerName "myComputer"

DESCRIPTION
-----------
Run uname against the remote computer

.EXAMPLE
$user = "testuser"
$userpw = Read-Host -Prompt "Enter Password" -AsSecureString
Invoke-SSHCommand -Command "touch /tmp/PowerShellHasBeenHere" -ComputerName "myComputer" -UserName $user -SecurePassword $userpw

DESCRIPTION
-----------
Creates a blank file in /tmp on the remote computer.  This time we are passing the user name and password
as parameters
#>

    [CmdletBinding()] param (
        [Parameter(Mandatory=$true)] [String]$ComputerName,
        [Parameter(Mandatory=$true)] [String]$Command,
        [Parameter(Mandatory=$false)] [String]$PathToPlink="C:\Program Files (x86)\PuTTY\plink.exe",
        [Parameter(Mandatory=$false)] [String]$UserName,
        [Parameter(Mandatory=$false)] [Security.SecureString]$SecurePassword,
        [Parameter(Mandatory=$false)] [Switch]$AutoAcceptKeys
    )

    process {
    
        #check for plink.exe file
        Write-Host ""
        if(! (Test-Path $PathToPlink) ) {
            Write-Host "ERROR: $PathToPlink not found" -ForegroundColor Magenta
            return
        }
        #get username if not passed
        if(!$UserName) {
            $UserName = Read-Host -Prompt "Enter User Name"
        }
        #get password if not passed
        if(!$SecurePassword) {
            $SecurePassword = Read-Host -Prompt "Enter Password" -AsSecureString
        }
        Write-Host ""
        
        #convert from secure pw for command execution
        $pw = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword))
        
        #formulate plink command
        if($AutoAcceptKeys) {
            $plinkCommand = "`"echo yes | `"$PathToPlink`" -ssh -l $UserName -pw $pw $ComputerName `"$Command`"`""
        }
        else {
            $plinkCommand = "`"`"$PathToPlink`" -ssh -l $UserName -pw $pw $ComputerName `"$Command`"`""
        }
        
        #run plink command
        $result = cmd /c $plinkCommand
        
        return $result
    }
}