VFP and .NET Interop
Running Powershell from VFP
Gravatar is a globally recognized avatar based on your email address. Running Powershell from VFP
  Alejandro A Sosa
  All
  May 22, 2017 @ 08:42am

In Doug Hennig's excellent article on Powershell he gives the following code as a way to instantiate Powershell from VFP using wwDotnetBridge.

* Instantiate wwDotNetBridge.
set procedure to wwDotNetBridge
loBridge = createobject('wwDotNetBridge', 'V4')
* Load the PowerShell assembly. Get the location using
* [psobject].assembly.location in PowerShell.
llReturn = loBridge.LoadAssembly('C:\Windows\Microsoft.Net\assembly\' + ;
'GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35' + ;
'\System.Management.Automation.dll')
if not llReturn
  messagebox(loBridge.cErrorMsg)
  return
endif not llReturn
* Create a PowerShell object.
loPS = loBridge.InvokeStaticMethod('System.Management.Automation.PowerShell','Create')

Is there a way to do it without the GAC_MSIL reference? Is the address above, including the v4.0_3.0.0.0__31bf3856ad364e35 part guaranteed to exist indefinitely?

Thank you very much

Alex

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Rick Strahl
  Alejandro A Sosa
  May 22, 2017 @ 11:41am

Why not just shell out to powershell.exe and add command line args to run what you need?

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Alejandro A Sosa
  Rick Strahl
  May 22, 2017 @ 12:26pm

Do you mean to use the vfp run or equivalent with wsh run ot exec and capture the output via a file?

If that is the only possibility ok, but I was wondering if is was possible to avoid the black screen.

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Rick Strahl
  Alejandro A Sosa
  May 22, 2017 @ 12:46pm

It depends on what you need to do.

You can use the CreateProcess() or CreateProcessEx API functions in the West Wind tools to cleanly shell out.

If you need to deal with the result output you can easily pipe to file, then read the file.

That said, I'm not really sure why you can't just use the DLL name on its own when it's supposed to be in the GAC. Those dlls should just be accessible.

Actually it is, but you need to use the fully qualified assembly name like this:

loBridge = GetwwDotnetBridge()

*** Fully qualified assembly name
llReturn = loBridge.LoadAssembly('System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')

if not llReturn
  messagebox(loBridge.cErrorMsg)
  return
endif not llReturn

* Create a PowerShell object.
loPS = loBridge.InvokeStaticMethod('System.Management.Automation.PowerShell','Create')
? loPS

+++ Rick ---

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Alejandro A Sosa
  Rick Strahl
  May 22, 2017 @ 06:21pm

Thank you very much Rick.

Only really getting into Powershell now in order to automate a few things in AWS. Totally AWeSome stuff. Still not finished.

Alex

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Alejandro A Sosa
  Rick Strahl
  May 23, 2017 @ 12:59pm

FWIW. I'd welcome a session on how to take advantage of Powershell with VS in SWFox.

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Michael B
  Alejandro A Sosa
  Apr 30, 2020 @ 06:38am

Hi Alejandro,

I wondered what cool stuff you were able to do from within WWWC out to PS since you first wrote this article. I am tasked with building some automated to do list type tasks inside my app and I was wondering if i should do it all inside of a WWWC app or try to automate the Task Scheduler. I could end up with thousands of tasks and I assumed it would make more sense to make it a dbf or sql table instead of 'trusting' the OS or bloating it. That said, I could not help but think the OS timer functions would be more stable than fox'.

Any thoughts on that?

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Alejandro A Sosa
  Michael B
  Apr 30, 2020 @ 04:02pm

Hi Michael B., (B ??)

Doug Henning wrote the article, not me. Here is what I have in my library in case it's useful to you.

I mostly kept using PS_ExecScript and PS_RunScript. Try what Rick wrote here.

Alex.

*************************************************************
* Codigo por Antonio Lopez
FUNCTION PS_ExecScript(tcScript)
*************************************************************
	LOCAL lnSeconds
	IF EMPTY(tcScript)
		RETURN "Script vacio"
	ENDIF

	LOCAL lcFile,lcStdOut,loWShell,lcCmd,lcSetEscape,loPS,lcText,lnSeconds
	lcFile = ADDBS(SYS(2023)) + SYS(2015) + ".ps1"
	lcStdOut = FORCEEXT(lcFile, "log")
	STRTOFILE(tcScript, lcFile)
	lcCmd = "cmd /c powershell -ExecutionPolicy RemoteSigned " + ;
							   "-File " + IIF(" " $ lcFile, '"' + lcFile + '"', lcFile) + ;
							   " > " + IIF(" "$ lcStdOut, '"' + lcStdOut + '"', lcStdOut)

	lcSetEscape = SET("Escape")
	SET ESCAPE ON
	loWShell = CREATEOBJECT("WScript.Shell")

	lnSeconds = SECONDS()
	lcCmd = "cmd /c powershell -ExecutionPolicy RemoteSigned " + ;
							   "-File " + IIF(" " $ lcFile, '"' + lcFile + '"', lcFile) + ;
							   " > " + IIF(" "$ lcStdOut, '"' + lcStdOut + '"', lcStdOut)
	loPS = loWShell.Exec(lcCmd)
	DO WHILE loPS.Status = 0
		* Durante los primeros 5 segundos no da mensaje
		IF SECONDS() - lnSeconds < 5
			MySleep(1000)
		ELSE
			WAIT WINDOW "Waiting for PowerShell" TIMEOUT 1
		ENDIF
	ENDDO

	WAIT CLEAR
	DOEVENTS
	SET ESCAPE &lcSetEscape

	* Si termina en CHR(13) + CHR(10) se lo quitamos
	TRY
		lcText = FILETOSTR(lcStdOut)
		lcText = IIF(RIGHT(lcText,2) = CHR(13) + CHR(10),LEFT(lcText,LEN(lcText)-2),lcText)
	CATCH
		lcText = ''
	ENDTRY
	ERASE (lcFile)
	ERASE (lcStdOut)
	RETURN lcText
ENDFUNC

*************************************************************
* Codigo por Antonio Lopez
FUNCTION PS_RunScript(tcScript,tnMillisecondsWait)
*************************************************************
	IF EMPTY(tcScript)
		RETURN "Script vacio"
	ENDIF
* 27/04/19 CLOUD2 No queremos riesgo que no espere nada
	IF EMPTY(tnMillisecondsWait)
		tnMillisecondsWait = 500
	ENDIF
	LOCAL lcScript,lcFile,lcStdOut,loWShell,loPS,lcCmd,lcSetEscape,lnSeconds
	lcFile = ADDBS(SYS(2023)) + SYS(2015) + ".ps1"
	lcStdOut = FORCEEXT(lcFile, "log")
* 27/04/19 CLOUD2 Precaucion
	IF FILE(lcStdOut)
		DELETE FILE lcStdOut
	ENDIF
	STRTOFILE(tcScript, lcFile)
	loWShell = CREATEOBJECT("WScript.Shell")
	lcCmd = "cmd /c powershell -ExecutionPolicy RemoteSigned " ;
							+ "-File " + IIF(" " $ lcFile, '"' + lcFile + '"', lcFile) ;
							+ " > " + IIF(" " $ lcStdOut, '"' + lcStdOut + '"', lcStdOut)

	lcSetEscape = SET("Escape")
	SET ESCAPE ON
	lnPS = loWShell.Run(lcCmd,0)

	lnSeconds = SECONDS()
	DO WHILE !FILE(lcStdOut) AND SECONDS() < lnSeconds + tnMillisecondsWait/1000
		* Durante los primeros 5 segundos no da mensaje
		IF SECONDS() - lnSeconds < 5 
			MySleep(1000)
		ELSE
			WAIT WINDOW "Waiting for PowerShell" TIMEOUT 1
		ENDIF
	ENDDO
	WAIT CLEAR
	DOEVENTS
	SET ESCAPE &lcSetEscape

	* Si termina en CHR(13) + CHR(10) se lo quitamos
	TRY
		lcText = FILETOSTR(lcStdOut)
		lcText = IIF(RIGHT(lcText,2) = CHR(13) + CHR(10),LEFT(lcText,LEN(lcText)-2),lcText)
	CATCH
		lcText = 'No pude leer el archivo' + lcStdOut
	ENDTRY
	* A veces estos comandos dan mensaje de archivo ocupado
	TRY
		ERASE (lcFile)
	CATCH
	ENDTRY
	TRY
		ERASE (lcStdOut)
	CATCH
	ENDTRY
	RETURN lcText
ENDFUNC

* By Doug Henning
************************************************************************
FUNCTION PS_GetPowershellObject
************************************************************************
	LOCAL loBridge,llReturn,loPS
	* Instantiate wwDotNetBridge.
	SET PROCEDURE TO wwDotNetBridge ADDITIVE
	loBridge = CREATEOBJECT('wwDotNetBridge', 'V4')	
	* Load the PowerShell assembly. Get the location using
	* [psobject].assembly.location in PowerShell.
	llReturn = loBridge.LoadAssembly('C:\Windows\Microsoft.Net\assembly\' + ;
	'GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35' + ;
	'\System.Management.Automation.dll')
	if not llReturn
	  messagebox(loBridge.cErrorMsg)
	  return
	endif not llReturn
	* Create a PowerShell object.
	loPS = loBridge.InvokeStaticMethod('System.Management.Automation.PowerShell','Create')
	RETURN loPS
ENDFUNC


* http://doughennig.com/papers/Pub/PowerShell.pdf
************************************************************************
FUNCTION PS_Hennig
************************************************************************
* Instantiate wwDotNetBridge.
set procedure to wwDotNetBridge
loBridge = createobject('wwDotNetBridge', 'V4')

* Load the PowerShell assembly. Get the location using
* [psobject].assembly.location in PowerShell.
llReturn = loBridge.LoadAssembly('C:\Windows\Microsoft.Net\assembly\' + ;
'GAC_MSIL\System.Management.Automation\v4.0_3.0.0.0__31bf3856ad364e35' + ;
'\System.Management.Automation.dll')
if not llReturn
	messagebox(loBridge.cErrorMsg)
	return
endif not llReturn

* Create a PowerShell object.
loPS = loBridge.InvokeStaticMethod('System.Management.Automation.PowerShell','Create')

* Add a command to execute. You can also call AddScript to add a script.
loBridge.InvokeMethod(loPS, 'AddCommand', 'Get-Process')

* We want to call the Invoke method of the PowerShell object but the following
* statement gives an "ambigous match found" error because there are two
* overloads of Invoke that accept no parameters but with different return
* values and wwDotNetBridge can't tell which to call.
*
* loResult = oBridge.InvokeMethod(loPS, 'Invoke')
*
* Instead, we'll go through the methods of the PowerShell object, look for the
* first Invoke method, and call it. It returns Collection<PSObject>. Note that
* we need an empty array for the second parameter of Invoke.
loType    = loBridge.InvokeMethod(loPS, 'GetType')
loMethods = loBridge.InvokeMethod(loType, 'GetMethods')
loArray   = loBridge.CreateArray()
for lnI = 0 to loMethods.Count-1
	loMethod = loMethods.Item(lnI)
	if loMethod.Name = 'Invoke'
		loResult = loBridge.InvokeMethod(loMethod, 'Invoke', loPS, loArray)
		exit
	endif loMethod.Name = 'Invoke'
next lnI

* Create a cursor to hold the process information.
create cursor Processes (Name C(50), ID I)
* Copy the result collection to an array and process each entry.
loArray.FromEnumerable(loResult)
for lnI = 0 to loArray.Count-1
	loItem = loArray.Item(lnI)
	* We want to get the ProcessName member of the object but this doesn't work:
	* Members is a PSMemberInfoCollection<PSMemberInfo> but accessing it like this
	* gives the collection, not the PSPropertyInfo object.
	*
	* loName    = loBridge.GetPropertyEx(loProcess, 'Members["ProcessName"]')
	*
	* Instead, get the Members collection and go through it, looking for the
	* members we're interested in.
	loMemb    = loBridge.GetPropertyEx(loItem, 'Members')
	loMembers = loBridge.CreateArray()
	loMembers.FromEnumerable(loMemb)
	append blank
	for lnJ = 0 to loMembers .Count
		loMember = loMembers.Item(lnJ)
		try
			lcName = loBridge.GetPropertyEx(loMember, 'Name')
			do case
				case lcName = 'ProcessName'
					replace Name with loBridge.GetPropertyEx(loMember, 'Value')
				case lcName = 'Id'
					replace ID with loBridge.GetPropertyEx(loMember, 'Value')
			endcase
		catch
		endtry
	next lnJ
next lnI

* Display the processes.
go top
BROWSE

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Alejandro A Sosa
  Michael B
  May 8, 2020 @ 08:46am

Hi Michael,

I used these routines to use Powershell in AWS. They may idea starters for you.

Alex

*************************************************************
* Obtains metadata from EC2 instance. Only works in AWS
FUNCTION AWS_GetMetadata(tcName)
*************************************************************
TEXT NOSHOW
	  ami-id               The AMI ID used to launch this instance
	  ami-launch-index     The index of this instance in the reservation (per AMI).
	  ami-manifest-path    The manifest path of the AMI with which the instance was launched.
	  ancestor-ami-ids     The AMI IDs of any instances that were rebundled to create this AMI.
	  block-device-mapping Defines native device names to use when exposing virtual devices.
	  instance-id          The ID of this instance
	  instance-type        The type of instance to launch. For more information, see Instance Types.
	  local-hostname       The local hostname of the instance.
	  local-ipv4           Public IP address if launched with direct addressing; private IP address if launched with public addressing.
	  kernel-id            The ID of the kernel launched with this instance, if applicable.
	  availability-zone    The availability zone in which the instance launched. Same as placement
	  product-codes        Product codes associated with this instance.
	  public-hostname      The public hostname of the instance.
	  public-ipv4          NATted public IP Address
	  public-keys          Public keys. Only available if supplied at instance launch time
	  ramdisk-id           The ID of the RAM disk launched with this instance, if applicable.
	  reservation-id       ID of the reservation.
	  security-groups      Names of the security groups the instance is launched in. Only available if supplied at instance launch time
	  user-data            User-supplied data.Only available if supplied at instance launch time.
ENDTEXT
************************************************************************
	tcName = IIF(EMPTY(tcName),'instance-id',tcName)
	TEXT TO lcPS1 TEXTMERGE NOSHOW
    #----------------------------------- Gets value of AWS EC2 instance metadata --------------------------------------------------------------- 
    Function get-metadata 
    { 
        try   { (New-Object System.Net.WebClient).DownloadString("http://169.254.169.254/latest/meta-data/<<tcName>>") } 
        catch { '(Error)' } 
    }
	get-metadata
	ENDTEXT

	lcText = PS_RunScript(lcPs1,30000)
	RETURN lcText
	*lcValue = PS_ExecScript(lcCommand)
	lcValue = PS_RunScript(lcCommand,5000)
RETURN lcValue


*************************************************************
FUNCTION AWS_IsAWS
*************************************************************
	TEXT TO lcPS1 NOSHOW
    #-----------------------------------Detects if a Host is running on AWS EC2--------------------------------------------------------------- 
    Function Test-AWSEC2 
    { 
        $error.clear() 
        $request = [System.Net.WebRequest]::Create('http://169.254.169.254/') 
        $request.Timeout = 900 
        try 
        { 
            $response = $request.GetResponse() 
            $response.Close() 
        } 
        catch { $false } 
        if (!$error) 
        { 
            $true 
        } 
    }
	Test-AWSEC2
	ENDTEXT
	lcText = PS_RunScript(lcPs1,30000)
	RETURN lcText = 'True'
ENDFUNC

Gravatar is a globally recognized avatar based on your email address. re: Running Powershell from VFP
  Michael B
  Alejandro A Sosa
  Jun 28, 2020 @ 01:29pm

Hi Alejandro,

Thank you so much for the sample and suggestions. I have not tried it yet, but I wondered if you might share what you are doing with AWS and your WWWC project. Are you hosting there?

Michael B

© 1996-2020