![]() |
![]() | ![]() | ![]() | ![]() | ![]() | ![]() | ![]() |
|
Welcome to Vista Forums we are your forum to discuss Windows Vista x64 and x86 systems. Whether you need help or just want to post an idea you have on Vista, this is the forum for you.
br> br> |
| |||||||
![]() |
| | Thread Tools | Display Modes |
| | #1 (permalink) |
| Guest | Non interactive process control manipulation Hello everyone. This might be more of a .Net question, but I'm ultimately using powershell, so I thought I'd start here. Some time ago, I posted here in trying to figure out a good way of automating installs and other processes on the computers at my work. (I'm a PC tech at a Community College). Many educational applications aren't written with automation in mind, so you end up pressing a lot of buttons in a GUI to get things done. Part of my challenge was that I wanted something that would work whether someone was signed into the computer or not. So, this meant I couldn't use sendkeys, since that required someone to be signed in and have the application in focus to be able to control it (at least it worked this way in VBScript when I used it, and it didn't seem like things had changed any). Ultimately, the method I ended up using is Win32 APIs called through .Net (which I then was referencing through powershell) where I used Findwindow, FindwindowEX, and Sendmessage to control the applications that I couldn't manage any other way. I have a scheduled task that I run Sunday mornings (around 1:00 am) that does initial installs, and installs any updated applications since the last run. For the most part, this worked fine. Generally, I only used canned keystrokes when I had to, and when I did, things worked well enough. But, just recently, I've come across some applications that just don't want to work. Ultimately, I think that the problem comes down to the fact that since the apps that I'm running are scheduled tasks they don't interact with the desktop. The reason I've come to this conclusion is that I can run the script and I see the application running in the process list of the task manager. However, I can't find the application using Winspector Spy, and Findwindow doesn't find any windows of the applications. Now, I've run some other installers this way, and it's worked out, but most of the time these are self extracting application - so I start one executable, but I end up manipulating another one, so I think the behavior might be different enough to get around this problem. (Also, I try to use MSI files when I can, so they tend to be well behaved). But, the script itself works - if I run it normally, as myself, it runs fine and I can see all of the windows as they pop up. So, I know it's pushing all of the buttons it's supposed to, at least when it can find them anyway. Here is some of my code to give everyone a feeling about what I'm doing ****************************************************** function Install-Program #Calls the installer executable, calls a redirected input function if needed, and waits until the program in done installing #Finally, the results of the install are then coded into the Autoinstall registry key for this particular program #One note, the final variable is not cast as a string. If cast as a string, it will become an empty string, which is not the same as $null { Param ([string]$InstallServerLocation, [string]$InstallFilePath, [string]$InstallProgram, [string]$Arguments, [string]$AutoInstallVersion, [string]$AutoInstallKey, [string]$ProgramKey, $FilePath = $null) Set-ItemProperty -Path ($AutoInstallKey + $ProgramKey) -Name "LastRun" -Value (get-date -format g) $Installer = new-object System.Diagnostics.ProcessStartInfo $Installer.filename = ($InstallServerLocation + $InstallFilePath + $InstallProgram) $Installer.Arguments = $Arguments $Installer.RedirectStandardInput = $false $Installer.RedirectStandardOutput = $false $Installer.RedirectStandardError = $false $Installer.LoadUserProfile = $false $Installer.UseShellExecute = $false $Installer.CreateNoWindow = $false $Installer.WindowStyle = ProcessWindowStyle.Normal $Installerexec = [System.Diagnostics.Process]::Start($Installer) ######################################################### #If redirected Input for a Win32 application is needed ######################################################### compile-CSharp $WinAPIcode #Compiles the C# Code for sendmessage and findwindow - currently commented out as it is not needed for this install start-sleep -s 180 #Waits x seconds to allow the executable time to get started Start-Input #The hard coded script for sending the messages - currently commented out as it is not needed for this install ######################################################### #After redirected Input for a Win32 application is needed ######################################################### $Installerexec.WaitForExit(1800000) #Makes sure the installer is done before proceeding to the next command - this may not work with installers that then call other programs Set-ItemProperty -Path ($AutoInstallKey + $ProgramKey) -Name "Exit Code" -Value $LASTEXITCODE if ($FilePath -ne $null) #Checks to see if $FilePath is assigned to anything, if it is, get a version from the file provided { Set-ItemProperty -Path ($AutoInstallKey + $ProgramKey) -Name "Version" -Value (Check-FileVersion $FilePath) #getting version information from the file provided Write-host "Program Version from File: $FilePath" } else { Set-ItemProperty -Path ($AutoInstallKey + $ProgramKey) -Name "Version" -Value (Check-InstalledVersion $ProgramKey) #Otherwise, get the version information from the Add Remove programs listing using WMI Write-host "Program Version from WMI: $ProgramKey" } Set-ItemProperty -Path ($AutoInstallKey + $ProgramKey) -Name "AutoInstallVersion" -Value $AutoInstallVersion } ****************************************** ######################################################### # C# Code to be compiled # # This uses the Win32API to find windows - Findwindow, find sub windows - FindWindowEx, and send messages to windows - sendmessage # The purpose of this is to provide manual keystroke input - or send messages between windows as though it had occurred # without having the problems of using SendKeys (Problems such as window focus, and losing focus to another applicaton). And, finally # having to be logged in to send these messages at all # # The C# code is included as a literal string - powershell doesn't interperate this at all ######################################################### $WinAPIcode = ' using System; using System.Runtime.InteropServices; namespace Win32APIStuff { public class CShWinAPI { [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindow(string lpClassName, string lpWindowName); [DllImport("user32.dll", SetLastError = true)] static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChild, string lpClassName, string lpWindowName); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam); public static IntPtr RunFindWindow(string ClassNm, string WindowNm) { IntPtr FindWindowVar; FindWindowVar = FindWindow(ClassNm, WindowNm); return FindWindowVar; } public static IntPtr RunFindWindowEx(IntPtr hWdParent, string ClassNm, string WindowNm) { IntPtr FindWindowVar; FindWindowVar = FindWindowEx(hWdParent, System.IntPtr.Zero, ClassNm, WindowNm); return FindWindowVar; } public static IntPtr RunSendMessage(IntPtr Window, uint Message, IntPtr wPar, IntPtr lPar) { IntPtr Sent; Sent = SendMessage(Window, Message, wPar, lPar); return Sent; } public static bool RunPostMessage(IntPtr Window, uint Message, IntPtr wPar, IntPtr lPar) { bool Sent; Sent = PostMessage(Window, Message, wPar, lPar); return Sent; } public static IntPtr MakeParam(int xCoordinate, int yCoordinate) { return (IntPtr) (((short)yCoordinate << 16) | (xCoordinate & 0xffff)); } } } ' ######################################################### #End of C# Code to be compiled ######################################################### *********************************************** This is the start of my Start-Input function so you can see how I'm sending the messages. ****************************************** function Start-Input #This function sends the redirected input to the particular window needed #It can also be used to send the messages back to the calling window #For instance, rather than 'hitting' a key to close a dialog box and send a message back to the calling window, #the resulting message, can be send directly to the calling window { ####### #First Screen ####### $FoundParent = [Win32APIStuff.CShWinAPI]::RunFindWindow('#32770', 'Create Storage Files') write-host $FoundParent $FoundWindow = [Win32APIStuff.CShWinAPI]::RunFindWindowEx($FoundParent, 'Button', 'Continue') write-host $FoundWindow [Win32APIStuff.CShWinAPI]::RunPostMessage($FoundParent, 0x111, ([Win32APIStuff.CShWinAPI]::MakeParam('1003','0')), $FoundWindow) start-sleep -m 500 start-sleep -s 60 ####### #Second Screen ####### $FoundParent = [Win32APIStuff.CShWinAPI]::RunFindWindow('#32770', 'Configure Storage Area') ******************************************** Basically, I take a block of C# code and compile it, which I can then call from powershell. The C# code allows me to access the Win32 API, so I can run findwindow, and sendmessage. I don't think the problem is actually with the message sending portion of the code, I just wanted everyone to be able to see what it was doing (and sort of get it out of the way so we can worry about where I think the real problem lies). What I believe the real problem is, is System.Diagnostics.ProcessStartInfo. Specifically, I'm looking for a way to allow the application to interact with the desktop, so I can access everything else that I need. I've messed around with a combination of LoadUserProfile, UseShellExecute, CreateNoWindow, and WindowStyle to try to create a application that Interacts with the desktop, but it doesn't seem to have worked. I know that I can specify a Username and password as part of the ProcessStartInfo, but I've been trying to avoid it. If nothing else, the scripts themselves are plain text, and I don't want to have an administrator account's password visible. I am running this as a specific user, but I'm doing it as part of the scheduled task. I have a domain user defined that's a local administrator on the computers that this script runs on. So, that's my problem in a nutshell. Anyone have any suggestions or advice? Thanks, Bill V. |
My System Specs![]() |
| | #2 (permalink) | ||||||||||||
| Guest | Re: Non interactive process control manipulation It sounds a fairly elaborate approach, but I won't question that ;-) There's a minor error at this line that I can spot $Installer.WindowStyle = ProcessWindowStyle.Normal which should probably be ..$Installer.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal but that in itself probably wouldn't be sufficient to throw it out completely. It it works when you run it manually but it's not even visible as a scheduled task, then that suggests to me that the scheduled task isn't set up correctly. You could try scheduling a simpler script such as the following. If you could get that to appear ok, then it may help you in troubleshooting your main procedure. #----------------- $NotepadProcess = new-object System.Diagnostics.ProcessStartInfo $NotepadProcess.filename = "notepad.exe" $NotepadProcess.Arguments = "" $NotepadProcess.RedirectStandardInput = $false $NotepadProcess.RedirectStandardOutput = $false $NotepadProcess.RedirectStandardError = $false $NotepadProcess.LoadUserProfile = $false $NotepadProcess.UseShellExecute = $false $NotepadProcess.CreateNoWindow = $false $NotepadProcess.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal $NotepadProcessexec = [System.Diagnostics.Process]::Start($NotepadProcess) #----------------- -- Jon "Bill V." <BillV@xxxxxx> wrote in message news:E607AF17-57C5-4489-9355-A8866DB3C88D@xxxxxx
| ||||||||||||
My System Specs![]() | |||||||||||||
| | #3 (permalink) | ||||||||||||
| Guest | Re: Non interactive process control manipulation Oops, I should elaborate a bit. It does start the program. When I go into the task manager, I can see the executable running. But, using Winspector Spy (I didn't have a copy of Spy ++) I can't find any of it's windows. Also, the findwindow function returns a 0, meaning it failed to find the window as well. Don't worry, I'd definitely say it's overly elaborate as well. I'm sure that it could be cleaned up quite a bit, and that there's things that could be done better. Sadly, "faking it" seems to be a fairly big part of scripting, and I hadn't found anyone else who was doing quite the same thing. But, I wanted a way to keep the computers here up to date, and to do so I needed an automated way of going about things (otherwise it never happens until theres a massive update and computers are reimaged. But then, the same thing starts happening and the computer start getting out of date again...). Also, though it's hard to tell from this snippit of code, I tried to write things so it was fairly easy to update for new programs. Often, I can just update a few variables at the begining of the code, and update a switch statement at the end of it, and I have something that works for a whole new piece of software (I've been able to manage perhaps 20 + programs this way, not counting the regular updates to plugins that occur regularly). Previously, I had done some minor things with VBScript, but powershell can do things in a much more concise manner, it's a shame to go back (and redirecting input doesn't work the way I want it to). I'll check out the code you provided and see how it goes, but I don't think it's a so much a matter of exactly how I'm starting the process (since I've successfully gotten several of them to work so far), but that this particular process seems to behave differently than the others I've dealt with. I've been looking into using CreateProcess from the Win API, but it seems a bit messy - I hate that I can't stick to .Net and powershell as it is. Thanks for your help! -Bill V. "Jon" wrote:
| ||||||||||||
My System Specs![]() | |||||||||||||
| | #4 (permalink) | ||||||||||||
| Guest | Re: Non interactive process control manipulation Quick remark on a side comment you made - you may find it useful, even though it doesn’t deal with the current problem you're trying to handle. "Bill V." <BillV@xxxxxx> wrote in message news:6EED50E2-7697-44EE-8576-F699763C3BFD@xxxxxx
quite a bit, and the stdin problem does have a solution. I'm assuming you see the input pipeline break when you run a WSH script within a 32-bit cmd.exe pipeline, kind of like this <somecommand> | somescript.vbs This breaks input, even if CScript is set as the default WSH host. The problem is that the shell API used for process creation initially assumes it is dealing with an executable, and when the process creation fails, the shell then tries to find a document handler for somescript.vbs and launches that application with the script name and the script's arguments as the handler's arguments - but it never hooks up the input pipeline. The same thing happens to Perl scripts, for example, which is why there are batch file wrappers for console Perl scripts in Windows Perl distributions. If you _are_ occasionally using WSH scripts for things, you can work around this in various ways, depending on what you have available. + If you use WSH scripts from within PowerShell, they do get stdin properly handled when cscript is the default host - this is one of the helper features built in to PowerShell for other script languages. + The 64-bit version of cmd.exe also fixes this problem, it appears; if cscript is the default host, WSH scripts don't get broken input streams. + You can explicitly specify cscript on the command line; this works, but it can be awkward because you need to specify the path to the script even if it is in your search path. + The low-maintenance way is to do what Perl does: create a wrapper script for the WSH script. What I do is simply create a .cmd file with the same base name as a WSH script, in the same folder, containing the following single line: @cscript //Nologo %~dpn0.vbs %* In this case, I can just specify the name "somescript" and somescript.cmd gets found by command search before somescript.vbs; it then performs the explicit script launch with cscript no matter what the default script host is. | ||||||||||||
My System Specs![]() | |||||||||||||
![]() |
| Thread Tools | |
| Display Modes | |
| |
Similar Threads | ||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| Start an interactive process under Local Syste | Ondrej Sevecek | Vista security | 1 | 04-21-2008 12:36 PM |
| Interactive powershell host or start a new process | ghandi | PowerShell | 11 | 03-24-2008 11:07 PM |
| Interactive logon process initialization has failed. | RCC | Vista account administration | 1 | 03-05-2008 04:16 AM |
| Interactive Logon Process failed. (Windows Vista) | CJL | Vista performance & maintenance | 1 | 10-11-2007 12:51 AM |
| interactive logon process initialization has failed | wyocowboy | Vista General | 4 | 05-07-2007 04:09 PM |