Full shell (explorer.exe) running for a RemoteApp on Terminal server - vba

Folks,
I'll been fighting this for a few weeks now. We have a proprietary corporate app running on thin clients in our organization. From this app we can apply VBA scripting behind the scenes to do simple functions. In the past adding buttons to open IE and direct them to certain web sites to enter data, open Calc and etc.
Recently I have been working the code below. It was developed on the Server using a RDP session which launches the full desktop and explorer.exe. What I have noticed that if explorer.exe isn't running then the code bombs at line in the function that reads "For Each oWin In oShApp.Windows " with "Run-time error '429'".
However if I script to start explorer.exe (which is bad because it enables the Start-bar and Task-bar) on the ThinClient which launches the desktop and full capabilities and it runs just fine as the Thin Client users that are logged in.
I have read a little bit about the "Limited Shell" that runs when using a remoteapp and not the full desktop and was wondering if there was anyway around it, without enable the start-menu and taskbar?
See the code below.
Thanks for the help, CreteIT
Private Sub cmdHomePage_Click()
Dim ie As Object
Dim sMatch As String
sMatch = "http://www.companyweb.com"
On Error Resume Next
Set ie = GetIEatURL(sMatch & "*")
On Error GoTo 0
If Not ie Is Nothing Then
ShowWindow ie.hwnd, 9
BringWindowToTop ie.hwnd
Else
Dim strHome
Dim strAPP
strHome = "c:\progra~1\intern~1\iexplore.exe www.companyweb.com"
strAPP = Shell(strHome, vbNormalFocus)
End If
End Sub
Function GetIEatURL(sMatch As String) As Object
Dim ie As Object, oShApp As Object, oWin As Object
Set oShApp = CreateObject("Shell.Application")
For Each oWin In oShApp.Windows
If TypeName(oWin.Document) = "HTMLDocument" Then
Set ie = oWin
If LCase(ie.LocationURL) Like LCase(sMatch) Then
Set GetIEatURL = ie
Exit For
End If
End If
Next
Set oShApp = Nothing
Set oWin = Nothing
End Function

I work on the RemoteApp team at Microsoft. I'm not quite clear whether you're running this script from an actual RemoteApp session or your own setup that has a different shell that replaces explorer.exe (or perhaps no shell at all), but either way the answer is about the same.
The Shell.Application ActiveX object your script is trying to instantiate appears to be implemented by the shell - typically explorer.exe. If that isn't running, the COM runtime won't be able to create an instance of the object, and when you try to activate it you'll get an error.
The obvious solution is to run explorer.exe but, as you observed, that also includes the taskbar and start menu, which you might not want. RemoteApp runs its own shell replacement (rdpshell.exe), but that doesn't implement Shell.Application, so just running your application under RemoteApp won't fix the problem.
There are a few potential solutions I can think of:
If you're not using RemoteApp, you could write your own replacement shell that does implement Shell.Application. This is quite a lot of work and there might not be a lot of documentation around on how to do it properly. If you already have a replacement shell, you might be able to extend it to implement Shell.Application.
You could use a different method to enumerate windows. Unfortunately this probably means Win32 (via the EnumWindows function), which isn't directly accessible from VB scripts; you might have to create an ActiveX object that implements the behavior you want, and invoke it from the script.
Similar to option 2 but more heavyweight - you could make that ActiveX object implement the whole Shell.Application interface - even though it isn't actually the shell - and register it on the remote machine. I can't guarantee you won't run into problems if you do this though; it isn't something I've tried before.
None of these solutions are ideal, unfortunately, but hopefully something lets you do what you need to.

Related

lotusscript agent calling another lotusscript agent not working

1.) On a web form, I have a Notes button (not an HTML input or button tag).....it calls a Lotusscript agent using #Command([RunAgent];"agentname") command ....that works fine
2.) Last line of this calls another Lotusscript agent using "runonserver"
3.) This second agent tries to use "DocumentContext" to identify the current document, but it does not seem to be able to do this, an error I logged indicates this to be the case.
So the question I have is...how could I have a first agent run, and successfully use "DocumentContext" and then call a second agent, and then have THAT agent identify the SAME document as the first one used? This second agent has its own:
Dim s as new notessession
Dim db as notesdatabase
Dim thisdoc as notesdocument
set db = s.currentdatabase
...and then it attempts to set thisdoc with :
set thisdoc = s.DocumentContext
The second agent is used elsewhere as the primary agent (not getting called in a daisy-chain situation) and it all works fine.
Maybe there is a simple solution that I am just not thinking of at the moment. I know I can put two #Command([RunAgent]... commands behind the button, but that has its own challenges, so I am wondering if someone as some slick/clever idea of what I can do.
DocumentContext is a memory construct passed to the agent, so there is no such thing in the database.documentcontext. What you'll have to do is save the DocumentContext as a document and then pass the NoteID (not the UNID) to the second agent. See if this works for you.
https://www.ibm.com/developerworks/lotus/library/ls-Run_and_RunOnServer/index.html
Since you're trying to use the same agent from both a button and a RunOnServer call, and that can't work (as per #Duston's answer), your best bet is probably to move the bulk of that agent code into a sub or function in a script library, and then have two agents. One agent gets the document context as you do now, and passes it into the script library code. The other uses agent.paramaterID and getDocumentById and passes that into the script library code.

How to do asynchronous function call in vba

I have created custom object in Python using pywin32.
while calling that object takes some times.
Following is my try at making that asynchronous with respect to application stat so that while macro is running application doesn't freez.
Sub demo()
demofunction
End Sub
Function demofunction()
Dim demoobj As Object
Do While demoobj Is Nothing
DoEvents
Set demoobj = CreateObject("Python.DemoObj")
Loop
Debug.Print demoobj.Hello
End Function
Obviously the code is not working in terms of asynchronous run. Is this even possible any how?
Above code however working fine while freezing the application for a while.
Vba is single threaded but you can play some tricks to make it appear multi-threaded, after all rising star web server Node.js serves off of one thread but gives impression of multi-threaded.
1) So, you could put some Python component behind a web server and then use MSXML2.XMLHTTP60 asynchronously. Private WithEvents oXHR As MSXML2.XMLHTTP60 comes in handy here. (Note you need to declare this as a member of a class to use WithEvents)
2) You could shell out to a process (which gives you another thread) and call the Python component that way, then you use some Windows API calls to periodically check if the process has completed.

VBA / .Net download files from password protected website via Windows Security

I have a SharePoint test website (Azure SharePoint farm) which contains several image files and some document templates. I want to download the images to my local drive. I anticipate looping through a list of URLs, e.g. http://mySharePointSite/Sites/Images/Image01.png and downloading each in turn. I've tried URLDownloadToFile which works great if I put the images on a regular website but I'm unable to get past Windows Security with this approach.
I had a go at creating an InternetExplorer.Application Object (late binding) and can view my images but am unable to download them. Was trying ieApp.ExecWB but this throws a Run Time Error that seems insurmountable.
I've also tried WinHTTP.WinHTTPrequest.5.1 (StackOverflow 22051960) but, while that looked promising it returned a short string "???????" instead of an image.
I'm really hoping for a VBA solution and am wondering if Impersonate User might provide options, or if there are other options (excluding using SendKeys). Sadly I'm pushing the limits of my VBA knowledge and could really use some guidance.
Is it possible to download from share point from VBA?
If so how, am I missing something simple?
I can paste code, but really I'm not sure I have anything worth sharing. If this is not possible or unreliable in VBA then would be possible with VB.Net (my next steepest learning curve)?
Hope this helps someone. I ended up using Visual Studio to create a dll in VB.Net that I can call from VBA. VB.Net dll requires references to microsoft.sharepoint.client
And the VBA code requires a reference to the resulting dll.
Imports Microsoft.SharePoint.Client
Module Module1
Sub Main()
Dim myContext As Microsoft.SharePoint.Client.ClientContext
myContext = New ClientContext("http://TestSP-a.cloudapp.net/sites/Images/")
Dim myCredentials As New System.Net.NetworkCredential
'' I'm accessing a website from the outside so there
'' are no credentials on my PC. If on the same NW I
'' assume I can use the line below instead of the
'' 3 lines that follow.
'myCredentials = System.Net.CredentialCache.DefaultNetworkCredentials
myCredentials.UserName = "UserNameForSharePoint"
myCredentials.Password = "PassWordForSharePoint"
myCredentials.Domain = "" ' <-- Leave Blank
Dim mySiteSP As New Uri("http://TestSP-a.cloudapp.net/sites/Images/Image1.png")
Dim myTemp As String = "C:\temp\test.png"
My.Computer.Network.DownloadFile(mySiteSP, myTemp, myCredentials, True, 600000I, False)
End Sub
End Module

Getting Windows Service To Read Registry HKLM

I've been having a heck of a time trying to get this to work. I wrote both a service and a form application in VB.NET, both of which need to access a registry key to locate a computer that has my database on it. In the forms app it works great, but the service does not. I thought it was a permissions problem so I checked permissions on the server (Windows 2008) and they were fine - I even went as far as to run the service as the admin in case that was the problem but still no joy. Is there something wrong with my code?
I have tried ways to access the registry. The first method just sets the server variable to nothing and the second method the 'rk' variable ends up being nothing (telling me that it cannot even locate the sub key):
Dim server As String = My.Computer.Registry.GetValue("HKEY_LOCAL_MACHINE\SOFTWARE\EPS\XPV", "Server", Nothing)
and the second one is:
Dim rk As RegistryKey = Registry.LocalMachine.OpenSubKey("\SOFTWARE\EPS\XPV")
The service itself starts up just fine because I'm getting my log messages right after these lines that tell me the value is blank so it just comes down to figuring out why I cannot access the registry in the service even though I can in the forms app.
Either look under HKLM\SOFTWARE\Wow6432Node for your keys/values, or compile your application as 64bit.

Is there any way to display a Windows form BEFORE the "Startup" form is loaded in VB.NET?

My company's main software package includes a hefty configuration library which loads on startup. This config library includes some mandatory settings which, if not supplied (via command line arguments), cause the entire application to exit.
This has never been an issue for our users, who launch the software via scripts which have the needed command line arguments supplied automatically. But sometimes when debugging our software we developers forget to specify the necessary arguments in Visual Studio's debug options; it's then very annoying to be greeted with the message Config specification invalid -- missing required parameters X, Y, and Z -- shutting down (I'm paraphrasing, of course).
It's not really a big deal, just an annoyance. Still, I felt it worthwhile to throw together a little form to make this process a little less painful; it notifies the user which parameters are missing and allows him/her to specify values for those parameters directly on the form, without having to restart the application.
My intentions were good (I think?), but it seems I can't get this solution to actually work. The problem is that after I've launched our software with missing settings, the form pops up and prompts me as expected; but after I've entered the required parameters and it's time for the application to "really" start, I get this InvalidOperationException:
SetCompatibleTextRenderingDefault must
be called before the first
IWin32Window object is created in the
application.
I think I understand what's going on here: the VB.NET project I'm working on is doing something like this "behind the scenes"*:
Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Application.Run(New MainForm)
End Sub
That call to SetCompatibleTextRenderingDefault is, apparently, throwing an exception because a form was already created and displayed prior to its execution.
Is there any way around this? Is there perhaps a more "proper" solution to this problem that I'm not thinking of (i.e., should I not be trying to collect user input via a form at all)?
*This is a best guess based on what I've seen in C# WinForms projects. Strangely, unless I'm missing something, it seems that VB.NET WinForms projects completely hide this from the developer.
Do make sure that you have the application framework option turned off and Sub Main selected as the starting method. Make it look similar to this:
Sub Main(ByVal args() As String)
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
If args.Length = 0 Then
Using dlg As New OptionsDialog
If dlg.ShowDialog <> DialogResult.OK Then Return
'' Use dlg result...
End Using
End If
Application.Run(New MainForm)
End Sub
Perhaps you could use the static Debugger.IsAttached (or even a #DEBUG directive) in your program's "main" function that feeds in some input file (say an XML file) into your parsed args collection instead?