[Perspective] PowerShell Script + system.util.execute

Hello.
I’d like to know if there are any ways to receive a feedback from system.util.execute after executing a PowerShell script.

If not, I suppose my best option will be to write the output to a .log file and the read it with system.file.readFileAsString, right?

That seems to be the way to go i guess

What kind of commands are you running tho that require feedback?

We are using a RAID data storage system via Windows Storage Spaces and the project must show a pop-up if and when something messy is going on.

You will have to delay the reading of the file probably if you are executing things like that that require time… i have no idea how you would now when a process is done :confused:

The script execution time is actually pretty immediate, to be honest. I’m probably going to put a small delay before reading the .log file, just to be sure.

I’m pasting the PowerShell script if anyone is ever going to need it.

# Execute with:
# powershell "& ""path_to_file\raid_status.ps1"""
# powershell -File "path_to_file\raid_status"

# Write to log file.
#
# Parameters
# $Mode: if true, Write-File will log system messages.
# $FilePath: output file path.
# $Timestamp: current timestamp.
# $Value: output value.
function Write-File {
    param (
        [Parameter(Mandatory=$true, Position=0)]
        [bool]$Mode,
        [Parameter(Mandatory=$true, Position=1)]
        [string]$FilePath,
        [string]$Timestamp,
        [Parameter(Mandatory=$true, Position=3)]
        [string]$Value
    )

    if ($Mode) {
        Out-File -FilePath $FilePath -InputObject "[$Timestamp] $Value" -Append -Encoding ascii
    }
    else {
        Out-File -FilePath $FilePath -InputObject $Value -Encoding ascii
    }
}

# Log file path.
$LogFilePath = "$PSScriptRoot\storage_spaces.log"
# Status file path.
$StatusFilePath = "$PSScriptRoot\status.log"
# Current timestamp.
$Timestamp = Get-Date -Format "dd/MM/yyyy HH:mm:ss:fffffff K"

# Current physical disks' health status.
try {
    $BadDisks = get-physicaldisk | Where-Object {$_.HealthStatus -ne "Healthy"}
}
catch {
    $Message = "Command has failed: $($_.Exception.Message)"
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
	
    exit 1
}

if ($BadDisks) {
    $Message = "Check diagnostics: possible disk failure. | $BadDisks"
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
    Write-File $false $StatusFilePath $false
	
	return $false
}
else {
    $Message = "Healthy: no physical disk issues found."
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
    Write-File $false $StatusFilePath $true
}

# Current status of virtual drives.
try {
    $BadRAIDState = Get-VirtualDisk | where-object {$_.OperationalStatus -ne "OK"}
}
catch {
    $Message = "Command has failed: $($_.Exception.Message)"
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
	
    exit 1
}

if ($BadRAIDState) {
    $Message = "Check diagnostics: possible RAID system failure. | $BadRAIDState"
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
    Write-File $false $StatusFilePath $false
	
	return $false
}
else {
	$Message = "Healthy: no Windows Storage Spaces issues found."
    Write-File $true $LogFilePath -Timestamp $Timestamp $Message
    Write-File $false $StatusFilePath $true
	
	return $true
}
1 Like

Ignition’s scripting functions target the common case, usually. In this case, you want to use the java Process class directly. Full control over the created OS process, including the option to connect and work with standard pipes in parallel. So, no need for a log file. The process can just write to its standard output and the java side can read it.

1 Like

Thank you for your insight: I’ll look into it to understand how to make it work. May I ask for any example made in Ignition Perspective?

here is a simple example in python using subprocess, we use to execute powershell 7 via perspective.

def executePowerShell(commands):
    """
    Executes the given command(s) string in Windows PowerShell 7

    :param commands: The string of command(s) to execute.
        Example:
            '''
            hostname
            "test string"
            '''
        Yields:
            1ABCD-HOSTNAME
            test string
    :type commands: str
    :returns: The stdout and the return code from the PowerShell process.
        Example: ("ABCD-HOSTNAME\ntest string", 0)
    :rtype: tuple
    """
    import subprocess

    cmd = subprocess.Popen(['pwsh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
    stdout = cmd.communicate(commands)[0]
    return stdout, cmd.returncode
3 Likes

Thank you! I’m still pretty new to Ignition, so I’d like to understand a couple of things.

  • This is a client script, right?
  • By using import subprocess I don’t need to access Java Process class as suggested by @pturmel?

Yes. It can be used anywhere you can run a script. You could add it to the project library or re-write it directly into an action script.

You don't necessarily need the function to wrap it, but as you learn Ignition you will find you should be organizing you scripts for re-use where needed. Its not obvious early on, but spending time thinking about the structure of your code/scripts etc. goes a long way to keep things sane as your project grow.

Correct, scripting uses jython, so you can use java functions in addition to jython functions. To me it comes down to what your comfortable with, but sometimes you need to evaluate performance as well. Sometimes the Java might be harder to write, but gives you more control and better performance. Sometimes it comes down to skill/comfort level. I just know python better and others know java better. You'll see both approaches debated all over the forums :slight_smile: I've learned a lot of java stuff from @pturmel over the years for sure!

jython has bugs and poor performing implementations scattered through its library. I've long advocated the use of java equivalents instead of jython wherever possible. Anything to do with network traffic, datetime, and proper handling of international strings/bytes streams in particular.

So, I strongly encourage using java's Process over jython's subprocess. Especially if character encoding on the traffic matters.

ProcessBuilder example script:

4 Likes

Hello @PGriffith.
What r"string" means in Jython?
Thank you!

raw string.
can be used so one wouldnt have to escape backslashes\

3 Likes

Trying this script right now in 7.9 and it doesn't work as from what I understand .readAllBytes() was not introduced yet. Trying process.getInputStream().readAllBytes() yields AttributeError: 'java.io.BufferedInputStream' object has no attribute 'readAllBytes', I tried process.getInputStream().toString() but that is only giving me stuff like java.io.BufferedInputStream@64418d37. Do you know how can I do this in 7.9?

Edit:

I was able to accomplish it like this

from java.lang import ProcessBuilder
from java.io import ByteArrayOutputStream
import java.nio.charset.StandardCharsets as StandardCharsets
import jarray

commands = ['someCommandHere']
pb = ProcessBuilder(commands)
process = pb.start()

result = ByteArrayOutputStream()
buffer = jarray.zeros(1024, 'b')
while True:
    length = process.getInputStream().read(buffer)
    if length == -1:
        break
    result.write(buffer, 0, length)
print result.toString(StandardCharsets.UTF_8.name())

7.9 probably has IOUtils on the classpath; you could try one of these:
https://commons.apache.org/proper/commons-io/apidocs/org/apache/commons/io/IOUtils.html#toByteArray-java.io.InputStream-