I'm trying to click on the login button for the page in the below file / url.
https://ufile.io/nryil
For some reason my code doesn't work. I've been messing with it for a day now. I'm kind of new to powershell so I'm not sure if this is something that's not capable, or if I'm missing something, but this is the latest code.
$username = username
$password = password
$ie = New-Object -com InternetExplorer.Application
$ie.visible=$true
$ie.navigate("192.168.1.10/designs/imm/index.php")
while( $ie.busy){Start-Sleep 10}
$secLink = $ie.Document.getElementsByTagName('A') | Where-Object {$_.innerText -eq 'Continue to this website (not recommended).'}
$secLink.click()
while( $ie.busy){Start-Sleep 10}
while($ie.ReadyState -ne 4) {start-sleep -m 20}
$ie.Document.IHTMLDocument3_getElementById("user").value = $username
$ie.Document.IHTMLDocument3_getElementById("password").value = $password
$link = $ie.Document.IHTMLDocument3_getElementsByName("Login").click
$link.click
I also just downloaded IMacros free trial to see how it's responding. I captured the below
element = driver.findElement(By.id("btnLogin_label"));
element.click();
element = driver.findElement(By.class("dijitOffScreen"));
element.click();
I even tried the below code after seeing that and still got nothing.
$link = $ie.Document.IHTMLDocument3_getElementById('btnLogin_Label') | Where-Object {$_.class -eq 'dijitOffScreen'}
$link.click
If I switch it to the below I get a "overload definitions void click()"
$link = $ie.Document.IHTMLDocument3_getElementById('btnLogin_Label')
$link.click
Related
So i write Windows update'r automat in java. For fetching needed data from windows servers i am using jPowerShell and i have stamble apon weird problem while execute this script
Java calling PowerShell Script
PowerShell ps = PowerShell.openSession();
PowerShellResponse response;
response = ps.executeScript("C:\\Users\\Prezes\\Desktop\\IsUpdateToInstal.ps1");
System.out.println(response.getCommandOutput());
PowerShell Script
$pw = ConvertTo-SecureString 'password' -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -argumentList domein\login, $pw
Enter-PSSession -ComputerName IP -Credential $cred
$UpdateSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session"))
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")
$Critical = $SearchResult.updates | where { $_.MsrcSeverity -eq "Critical" }
$important = $SearchResult.updates | where { $_.MsrcSeverity -eq "Important" }
$other = $SearchResult.updates | where { $_.MsrcSeverity -eq $nul}
$totalUpdates = $($SearchResult.updates.count)
if($totalUpdates -gt 0)
{
$updatesToInstall = $true
}
else { $updatesToInstall = $false }
$other
$totalUpdates
$updatesToInstall
if i execute this script line by line in PowerShell standard consol everyting is working fine and proper value are returned.
But when i run this script in PowerShell ISE line by line or run by Java i notice some problem with this line
$UpdateSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session"))
while i enter this line and press enter i can see in ISE "already running a command, please wait" when i wait a faw minutes communicate is the same and nothing change but when i press enter secound time command pass through immediately. If from them now i run rest of script evertying i working well.
When i try to excecute full script in ISE i am geting this error
Exception form HRESULT: 0x80072EE2
At C:\Users\Prezes\Desktop\IsUpdateToInstal.ps1:6 char:1
+ $SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 a ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], COMException
+ FullyQualifiedErrorId : System.Runtime.InteropServices.COMExceptio
Java gives me null saying that i can't run methond on null object reffering to $UpdateSearcher
I am very early beginer with PowerShell and script that i am using is pure form some example finded in google.
So i had not figure out what was the cause of weird behavior but i managed to write someting that started to work for me through PowerShell api in java and get returned value.
$pw = ConvertTo-SecureString 'PASSWORD' -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -argumentList 792\opmanager, $pw
$TotalUpdates = Invoke-Command -ComputerName IP -ScriptBlock{
$UpdateSession = [activator]::CreateInstance([type]::GetTypeFromProgID("Microsoft.Update.Session"))
$UpdateSearcher = $UpdateSession.CreateUpdateSearcher()
$SearchResult = $UpdateSearcher.Search("IsAssigned=1 and IsHidden=0 and IsInstalled=0")
$Critical = $SearchResult.updates | where { $_.MsrcSeverity -eq "Critical" }
$important = $SearchResult.updates | where { $_.MsrcSeverity -eq "Important" }
$other = $SearchResult.updates | where { $_.MsrcSeverity -eq $nul}
$totalUpdates = $($SearchResult.updates.count)
if($totalUpdates -gt 0)
{
$updatesToInstall = $true
}
else { $updatesToInstall = $false }
Return $totalUpdates
} -Credential $cred
$TotalUpdates
I created a java application(.jar) as a windows service using Procrun. This Service get installed and runs successfully when I use a batch(.bat) file. but I have this requirement of creating the same service using windows powerShell.
When I use PowerShell, service get installed but cannot start the service I checked in the windows event viewer and it displays as "Incorrect Function" Can anyone please tell me what could be the reason for this.
This is the string I used in PowerShell script to install the windows service
$CmdInstall=#'
--Description="$Description"
--DisplayName="$DisplayName"
--Install="$DAEMON"
--Startup=auto
--Type=
--DependsOn=
--Environment=
--User=
--Password=
--ServiceUser=
--ServicePassword=
--LibraryPath=
--JavaHome
--Jvm=auto
--JvmOptions=-Xmx1024M
--Classpath=server.jar
--JvmMs=
--JvmMx=
--JvmSs=
--StartMode=jvm
--StartImage=
--StartPath=
--StartClass=at.mrdevelopment.esl.server.Server
--StartMethod=main
--StartParams=
--StopMode=jvm
--StopImage=
--StopPath=
--StopClass=at.mrdevelopment.esl.server.ServerServiceStarter
--StopMethod=stop
--StopParams=
--StopTimeout=120
--LogPath=$LogPath
--LogPrefix=$InstanceName
--LogLevel=DEBUG
--LogJniMessages=
--StdOutput=auto
--StdError=auto
--PidFile=${InstanceName}.pid
'#
Any help would be appreciated.
This is the PowerShell script I used.
#region Parameters
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[ValidateNotNullOrEmpty()]
[string]$Action="//IS"
,
[ValidateNotNullOrEmpty()]
[string]$ServiceName="//ESL_SERVICE"
,
[ValidateNotNullOrEmpty()]
[string]$DisplayName="ESL_SERVICE"
,
[ValidateNotNullOrEmpty()]
[string]$Description="ESL Service"
,
[ValidateNotNullOrEmpty()]
[string]$InstanceName="server.jar"
,
[ValidateNotNullOrEmpty()]
[string]$LogPath='C:\Apachelogs'
,
[string]$Pause=60
)
#endregion
#region Main
$CmdInstall=#'
--Description="$Description"
--DisplayName="$DisplayName"
--Install="$DAEMON"
--Startup=auto
--Type=
--DependsOn=
--Environment=
--User=
--Password=
--ServiceUser=
--ServicePassword=
--LibraryPath=
--JavaHome
--Jvm=auto
--JvmOptions=-Xmx1024M
--Classpath=server.jar
--JvmMs=
--JvmMx=
--JvmSs=
--StartMode=jvm
--StartImage=
--StartPath=
--StartClass=at.mrdevelopment.esl.server.Server
--StartMethod=main
--StartParams=
--StopMode=jvm
--StopImage=
--StopPath=
--StopClass=at.mrdevelopment.esl.server.ServerServiceStarter
--StopMethod=stop
--StopParams=
--StopTimeout=120
--LogPath=$LogPath
--LogPrefix=$InstanceName
--LogLevel=DEBUG
--LogJniMessages=
--StdOutput=auto
--StdError=auto
--PidFile=${InstanceName}.pid
'#
$DAEMON_HOME = "C:\imagotag\server"
$DAEMON = "$DAEMON_HOME\prunsrv_64.exe"
$ESL_HOME = "C:\imagotag\server"
$CmdArgsDict=#{}
$CmdArgsDict.Add('//IS', "$Action$ServiceName $CmdInstall")
$CmdArgs = $CmdArgsDict[$action]
# Convert backslashes in the paths to java-friendly forward slashes
$CmdArgs = $CmdArgs -replace "\\","/"
# Variable interpolation: expand embedded variables references (need to call this twice)
$CmdArgs = $ExecutionContext.InvokeCommand.ExpandString($CmdArgs)
$CmdArgs = $ExecutionContext.InvokeCommand.ExpandString($CmdArgs)
# Split on newlines to convert to an array of lines
$CmdArgsString = $CmdArgs -split "`n"
# Convert array of lines into a string
$CmdArgsString = "$CmdArgsString"
#--- Execute the command
if ($PSCmdlet.ShouldProcess(
"`n$DAEMON`n$CmdArgs","Manage ESL Service"
))
{
"$DAEMON $CmdArgsString"
$p=Start-Process "$DAEMON" `
-ArgumentList "$CmdArgsString" `
-Wait `
-NoNewWindow `
-PassThru
$rc = $p.ExitCode
"`nExit Code: $rc"
}
#endregion
My PowerShell script is TestPS.ps1 and I execute the script like this
.\TestPS.ps1 //IS
For creating a service , use the following cmdlet in PS:
$username = "Username"
$password = "password"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr
new-service -Name [INSERT SERVICE NAME] -DisplayName "[INSERT DISPLAY NAME]" -Description "[INSERT DESCRIPTION]" -BinaryPathName "[INSERT BINARY PATH]" -StartupType Manual -Credential $cred
If you wish to perform it remotely, then use the following function:
function Install-Service(
[string]$serviceName = $(throw "serviceName is required"),
[string]$targetServer = $(throw "targetServer is required"),
[string]$displayName = $(throw "displayName is required"),
[string]$physicalPath = $(throw "physicalPath is required"),
[string]$userName = $(throw "userName is required"),
[string]$password = "",
[string]$startMode = "Automatic",
[string]$description = "",
[bool]$interactWithDesktop = $false
)
{
# can't use installutil; only for installing services locally
#[wmiclass]"Win32_Service" | Get-Member -memberType Method | format-list -property:*
#[wmiclass]"Win32_Service"::Create( ... )
# todo: cleanup this section
$serviceType = 16 # OwnProcess
$serviceErrorControl = 1 # UserNotified
$loadOrderGroup = $null
$loadOrderGroupDepend = $null
$dependencies = $null
# description?
$params = `
$serviceName, `
$displayName, `
$physicalPath, `
$serviceType, `
$serviceErrorControl, `
$startMode, `
$interactWithDesktop, `
$userName, `
$password, `
$loadOrderGroup, `
$loadOrderGroupDepend, `
$dependencies `
$scope = new-object System.Management.ManagementScope("\\$targetServer\root\cimv2", `
(new-object System.Management.ConnectionOptions))
"Connecting to $targetServer"
$scope.Connect()
$mgt = new-object System.Management.ManagementClass($scope, `
(new-object System.Management.ManagementPath("Win32_Service")), `
(new-object System.Management.ObjectGetOptions))
$op = "service $serviceName ($physicalPath) on $targetServer"
"Installing $op"
$result = $mgt.InvokeMethod("Create", $params)
Test-ServiceResult -operation "Install $op" -result $result
"Installed $op"
"Setting $serviceName description to '$description'"
Set-Service -ComputerName $targetServer -Name $serviceName -Description $description
"Service install complete"
}
Here is the Guide to Install Windows Service Remotely
Note: Make sure you are running everything with elevated mode.
Hope it helps.
Your problems came from a misconfigured service. That comes from a problem with the parameters of prunsrv.exe.
The script works well after changing the line
$CmdArgsString = $CmdArgs -split "`n"
to
$CmdArgsString = $CmdArgs -split "`r`n"
I try to start an Java program from a PowerShell script, but I don't want see any window. I have already tried all possible combinations of parameters for ProcessStartInfo and Process, but it still displays a empty shell window for java.exe. This is my current PowerShell code:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "$jre\bin\java.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.CreateNoWindow = $true
$pinfo.WindowStyle = "Hidden"
$pinfo.UseShellExecute = $false
$pinfo.WorkingDirectory = $temp
if ($integrated) {
Write-Output "Connecting using integrated security ..."
# Start with different user context
$securePassword = ConvertTo-SecureString $pw -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential $user, $securePassword
$pinfo.UserName = $credential.GetNetworkCredential().UserName
$pinfo.Domain = $credential.GetNetworkCredential().Domain
$pinfo.Password = $credential.Password
$pinfo.Arguments = "-jar `"$jar`" validate -url `"$jdbc`" -i -dbVersion `"$msSqlVersion`""
} else {
Write-Output "Connecting ..."
$pinfo.Arguments = "-jar `"$jar`" validate -url `"$jdbc`" -u `"$user`" -p `"$pw`" -dbVersion `"$dbVersion`""
}
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start()
$p.StandardOutput.ReadToEnd()
$p.StandardError.ReadToEnd()
$p.WaitForExit()
I hope anyone could help me with this issue.
I need to download a file to a given location on a non-local machine. This is the normal flow of the web browser for which I would do this:
Go to website
Click button to download file (it is a form that generates the file, it is not a download link)
The website prompts an alert window "Do you want to download this file?", etc.
I want to be able to bypass the file and do something like:
>>> path_to_download_path = PATH
>>> button = driver.find_element_by_css("...")
>>> button.click()
--> And the file is automatically downloaded to my PATH (or wherever I choose)
Or is there an easier way that click, where I can automatically download the content of the file?
How would I do this?
You would have to examine the javascript on the website and understand how it works before you could override it to do something like that, but even then, browser security will always pop a dialog asking you to confirm the download. That leaves you with two options (as far as I can see):
Confirm the alert dialog
Determine the location of the file on the remote server, and use a GET to download the file
I can't really help with the details on either, since I don't know python, but hopefully that helps...
Use selenium webdriver
Use firefox profile to download your files. This profile skip that dialogue box of firefox.
In line:-
pro.setPreference("browser.downLoad.folderList", 0);
The value of browser.download.folderList can be set to either 0, 1, or 2. When set to 0, Firefox will save all files downloaded via the browser on the user's desktop. When set to 1, these downloads are stored in the Downloads folder. When set to 2, the location specified for the most recent download is utilized again.
Firefox profile code that you need to implement :-
FirefoxProfile pro=new FirefoxProfile();
pro.setPreference("browser.downLoad.folderList", 0);
pro.setPreference("browser.helperApps.neverAsk.saveToDisk", "Applications/zip");
WebDriver driver=new FirefoxDriver(pro);
driver.get("http://selenium-release.storage.googleapis.com/2.47/selenium-java-2.47.1.zip");
Hope it will help you :)
When you initialize your driver, be sure to set the download preferences.
For Firefox:
ff_prof.set_preference( "browser.download.manager.showWhenStarting", False )
ff_prof.set_preference( "browser.download.folderList", 2 )
ff_prof.set_preference( "browser.download.useDownloadDir", True )
ff_prof.set_preference( "browser.download.dir", self.driver_settings['download_folder'] )
##
# if FF still shows the download dialog, make sure that the filetype is included below
# filetype string options can be found in '~/.mozilla/$USER_PROFILE/mimeTypes.rdf'
##
mime_types = ("application/pdf", "text/html")
ff_prof.set_preference( "browser.helperApps.neverAsk.saveToDisk", (", ".join( mime_types )) )
ff_prof.set_preference( "browser.helperApps.neverAsk.openFile", (", ".join( mime_types )) )
For Chrome:
capabilities['chromeOptions']['prefs']['download.prompt_for_download'] = False
capabilities['chromeOptions']['prefs']['download.default_directory'] = self.driver_settings['download_folder']
Forwarding the download:
Below is the code I use to redirect the file from self.driver_settings['download_folder'] (set above) to where you actually want the file (to_path can be an existing folder or a filepath). If you're on linux, I'd suggest using tmpfs so that /tmp is held in ram and then set self.driver_settings['download_folder'] to "/tmp/driver_downloads/". Note that the below function assumes that self.driver_settings['download_folder'] always begins as an empty folder (this is how it locates the file being downloaded, since it's the only one in the directory).
def moveDriverDownload(self, to_path, allowable_extensions, allow_rename_if_exists=False, timeout_seconds=None):
if timeout_seconds is None:
timeout_seconds = 30
wait_delta = timedelta( seconds=timeout_seconds )
start_download_time = datetime.now()
hasTimedOut = lambda: datetime.now() - start_download_time > wait_delta
assert isinstance(allowable_extensions, list) or isinstance(allowable_extensions, tuple) or isinstance(allowable_extensions, set), "instead of a list, found allowable_extensions type of '{}'".format(type(allowable_extensions))
allowable_extensions = [ elem.lower().strip() for elem in allowable_extensions ]
allowable_extensions = [ elem if elem.startswith(".") else "."+elem for elem in allowable_extensions ]
if not ".part" in allowable_extensions:
allowable_extensions.append( ".part" )
re_extension_str = "(?:" + ("$)|(?:".join( re.escape(elem) for elem in allowable_extensions )) + "$)"
getFiles = lambda: next( os.walk( self.driver_settings['download_folder'] ) )[2]
while True:
if hasTimedOut():
del allowable_extensions[ allowable_extensions.index(".part") ]
raise DownloadTimeoutError( "timed out after {} seconds while waiting on file download with extension in {}".format(timeout_seconds, allowable_extensions) )
time.sleep( 0.5 )
file_list = [ elem for elem in getFiles() if re.search( re_extension_str, elem ) ]
if len(file_list) > 0:
break
file_list = [ re.search( r"(?i)^(.*?)(?:\.part)?$", elem ).groups()[0] for elem in file_list ]
if len(file_list) > 1:
if len(file_list) == 2:
if file_list[0] != file_list[1]:
raise Exception( "file_list[0] != file_list[1] <==> {} != {}".format(file_list[0], file_list[1]) )
else:
raise Exception( "len(file_list) > 1. found {}".format(file_list) )
file_path = "%s%s" %(self.driver_settings['download_folder'], file_list[0])
# see if the file is still being downloaded by checking if it's open by any programs
if platform.system() == "Linux":
openProcess = lambda: subprocess.Popen( 'lsof | grep "%s"' %file_path, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE )
fileIsFinished = lambda txt: txt.strip() == ""
elif platform.system() == "Windows":
# 'handle' program must be in PATH
# https://technet.microsoft.com/en-us/sysinternals/bb896655
openProcess = lambda: subprocess.Popen( 'handle "%s"' %file_path.replace("/", "\\"), shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE )
fileIsFinished = lambda txt: bool( re.search("(?i)No matching handles found", txt) )
else:
raise Exception( "unrecognised platform.system() of '{}'".format(platform.system()) )
while True:
lsof_process = openProcess()
lsof_result = lsof_process.communicate()
if len(lsof_result) != 2:
raise Exception( "len(lsof_result) != 2. found {}".format(lsof_result) )
if lsof_result[1].strip() != "":
raise Exception( 'lsof_result[1].strip() != "". found {}'.format(lsof_result) )
if fileIsFinished( lsof_result[0] ):
break
if hasTimedOut():
raise Exception( "timed out after {} seconds waiting for '{}' to be freed from writing. found lsof/handle of '{}'".format(timeout_seconds, file_path, lsof_result[0]) )
time.sleep( 0.5 )
to_path = to_path.replace("\\", "/")
if os.path.isdir( to_path ):
if not to_path.endswith("/"):
to_path += "/"
to_path += file_list[0]
i = 2
while os.path.exists( to_path ):
if not allow_rename_if_exists:
raise Exception( "{} already exists".format(to_path) )
to_path = re.sub( "^(.*/)(.*?)(?:-" + str(i-1) + r")?(|\..*?)?$", r"\1\2-%i\3" %i, to_path )
i += 1
shutil.move( file_path, to_path )
return to_path[ to_path.rindex("/")+1: ]
I want to start a Java program from PowerShell and get the results printed on the console.
I have followed the instructions of this question:
Capturing standard out and error with Start-Process
But for me, this is not working as I expected. What I'm doing wrong?
This is the script:
$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.CreateNoWindow = $true
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.FileName = 'java.exe'
$psi.Arguments = #("-jar","tools\compiler.jar","--compilation_level", "ADVANCED_OPTIMIZATIONS", "--js", $BuildFile, "--js_output_file", $BuildMinFile)
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
$process.Start() | Out-Null
$process.WaitForExit()
$output = $process.StandardOutput.ReadToEnd()
$output
The $output variable is always empty (and nothing is printed on the console of course).
The docs on the RedirectStandardError property suggests that it is better to put the WaitForExit() call after the ReadToEnd() call. The following works correctly for me:
$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.CreateNoWindow = $true
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.FileName = 'ipconfig.exe'
$psi.Arguments = #("/a")
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
[void]$process.Start()
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()
$output
Small variation so that you can selectively print the output if needed. As in if your looking just for error or warning messages and by the way Keith you saved my bacon with your response...
$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.CreateNoWindow = $true
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.FileName = 'robocopy'
$psi.Arguments = #("$HomeDirectory $NewHomeDirectory /MIR /XF desktop.ini /XD VDI /R:0 /W:0 /s /v /np")
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
[void]$process.Start()
do
{
$process.StandardOutput.ReadLine()
}
while (!$process.HasExited)
Here is a modification to paul's answer, hopefully it addresses the truncated output. i did a test using a failure and did not see truncation.
function Start-ProcessWithOutput
{
param ([string]$Path,[string[]]$ArgumentList)
$Output = New-Object -TypeName System.Text.StringBuilder
$Error = New-Object -TypeName System.Text.StringBuilder
$psi = New-object System.Diagnostics.ProcessStartInfo
$psi.CreateNoWindow = $true
$psi.UseShellExecute = $false
$psi.RedirectStandardOutput = $true
$psi.RedirectStandardError = $true
$psi.FileName = $Path
if ($ArgumentList.Count -gt 0)
{
$psi.Arguments = $ArgumentList
}
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $psi
[void]$process.Start()
do
{
if (!$process.StandardOutput.EndOfStream)
{
[void]$Output.AppendLine($process.StandardOutput.ReadLine())
}
if (!$process.StandardError.EndOfStream)
{
[void]$Error.AppendLine($process.StandardError.ReadLine())
}
Start-Sleep -Milliseconds 10
} while (!$process.HasExited)
#read remainder
while (!$process.StandardOutput.EndOfStream)
{
#write-verbose 'read remaining output'
[void]$Output.AppendLine($process.StandardOutput.ReadLine())
}
while (!$process.StandardError.EndOfStream)
{
#write-verbose 'read remaining error'
[void]$Error.AppendLine($process.StandardError.ReadLine())
}
return #{ExitCode = $process.ExitCode; Output = $Output.ToString(); Error = $Error.ToString(); ExitTime=$process.ExitTime}
}
$p = Start-ProcessWithOutput "C:\Program Files\7-Zip\7z.exe" -ArgumentList "x","-y","-oE:\PowershellModules",$NewModules.FullName -verbose
$p.ExitCode
$p.Output
$p.Error
the 10ms sleep is to avoid spinning cpu when nothing to read.
I was getting the deadlock scenario mentioned by Ash using Justin's solution. Modified the script accordingly to subscribe to async event handlers to get the output and error text which accomplishes the same thing but avoids the deadlock condition.
Seemed to resolve the deadlock issue in my testing without altering the return data.
# Define global variables used in the Start-ProcessWithOutput function.
$global:processOutputStringGlobal = ""
$global:processErrorStringGlobal = ""
# Launch an executable and return the exitcode, output text, and error text of the process.
function Start-ProcessWithOutput
{
# Function requires a path to an executable and an optional list of arguments
param (
[Parameter(Mandatory=$true)] [string]$ExecutablePath,
[Parameter(Mandatory=$false)] [string[]]$ArgumentList
)
# Reset our global variables to an empty string in the event this process is called multiple times.
$global:processOutputStringGlobal = ""
$global:processErrorStringGlobal = ""
# Create the Process Info object which contains details about the process. We tell it to
# redirect standard output and error output which will be collected and stored in a variable.
$ProcessStartInfoObject = New-object System.Diagnostics.ProcessStartInfo
$ProcessStartInfoObject.FileName = $ExecutablePath
$ProcessStartInfoObject.CreateNoWindow = $true
$ProcessStartInfoObject.UseShellExecute = $false
$ProcessStartInfoObject.RedirectStandardOutput = $true
$ProcessStartInfoObject.RedirectStandardError = $true
# Add the arguments to the process info object if any were provided
if ($ArgumentList.Count -gt 0)
{
$ProcessStartInfoObject.Arguments = $ArgumentList
}
# Create the object that will represent the process
$Process = New-Object System.Diagnostics.Process
$Process.StartInfo = $ProcessStartInfoObject
# Define actions for the event handlers we will subscribe to in a moment. These are checking whether
# any data was sent by the event handler and updating the global variable if it is not null or empty.
$ProcessOutputEventAction = {
if ($null -ne $EventArgs.Data -and $EventArgs.Data -ne ""){
$global:processOutputStringGlobal += "$($EventArgs.Data)`r`n"
}
}
$ProcessErrorEventAction = {
if ($null -ne $EventArgs.Data -and $EventArgs.Data -ne ""){
$global:processErrorStringGlobal += "$($EventArgs.Data)`r`n"
}
}
# We need to create an event handler for the Process object. This will call the action defined above
# anytime that event is triggered. We are looking for output and error data received by the process
# and appending the global variables with those values.
Register-ObjectEvent -InputObject $Process -EventName "OutputDataReceived" -Action $ProcessOutputEventAction
Register-ObjectEvent -InputObject $Process -EventName "ErrorDataReceived" -Action $ProcessErrorEventAction
# Process starts here
[void]$Process.Start()
# This sets up an asyncronous task to read the console output from the process, which triggers the appropriate
# event, which we setup handlers for just above.
$Process.BeginErrorReadLine()
$Process.BeginOutputReadLine()
# Wait for the process to exit.
$Process.WaitForExit()
# We need to wait just a moment so the async tasks that are reading the output of the process can catch
# up. Not having this sleep here can cause the return values to be empty or incomplete. In my testing,
# it seemed like half a second was enough time to always get the data, but you may need to adjust accordingly.
Start-Sleep -Milliseconds 500
# Return an object that contains the exit code, output text, and error text.
return #{
ExitCode = $Process.ExitCode;
OutputString = $global:processOutputStringGlobal;
ErrorString = $global:processErrorStringGlobal;
ExitTime = $Process.ExitTime
}
}