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
Why not just shell out to powershell.exe
and add command line args to run what you need?
+++ Rick ---
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.
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 ---
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
FWIW. I'd welcome a session on how to take advantage of Powershell with VS in SWFox.
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?
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
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
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