<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://weblogs.foxite.com/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>fox weblog</title><link>http://weblogs.foxite.com/stuartdunkeld/default.aspx</link><description /><dc:language>en-GB</dc:language><generator>CommunityServer 2.0 (Build: 60217.2664)</generator><item><title>Automating Visual Studio from VFP</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2007/10/11/5142.aspx</link><pubDate>Thu, 11 Oct 2007 12:29:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:5142</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/5142.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=5142</wfw:commentRss><description>Updating a file in a Visual Studio project from VFP is pretty straightforward. 

First up, get a reference to the project:
&lt;pre&gt;oVS = createobject("VisualStudio.Solution.8.0") &amp;amp;&amp;amp; VS2005 &lt;br&gt;oVS.Open("C:\projects\example.sln")&lt;br&gt;oProject = oVS.Projects(1)&lt;br&gt;&lt;/pre&gt;
Each project has a ProjectItems collection: each can be a folder or a file. The items can be referenced by name, so this returns a reference to an XML file in the XML folder:
&lt;pre&gt;oDocument = oProject.ProjectItems("XML").ProjectItems("file.xml")&lt;/pre&gt;
Each item has a URL property which indicates (for a file) the file name, but this is only available via the Properties collection:
&lt;pre&gt;cFilePath = substr(oDocument.Properties("URL").Value, 9) &amp;amp;&amp;amp; strip off file:///&lt;br&gt;xmltocursor(alias(), "_cliptext") &amp;amp;&amp;amp; Get XML on the clipboard&lt;br&gt;strtofile(_cliptext, cFilePath) &amp;amp;&amp;amp; write to file.&lt;br&gt;&lt;/pre&gt;
You can do this while VS has the file open, it will notice and prompt you to reload the file.&lt;br&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=5142" width="1" height="1"&gt;</description></item><item><title>Slipstreaming VFP</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2007/03/27/3571.aspx</link><pubDate>Tue, 27 Mar 2007 11:12:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:3571</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/3571.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=3571</wfw:commentRss><description>The &lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=cc84f749-a153-4929-8277-deb9a2f8b0a5&amp;amp;DisplayLang=en"&gt;February 2007 CTP of VFP9 SP2&lt;/a&gt; can be slipstreamed into the original VFP installation: this means that it's integrated into the setup process so it will directly install VFP9 SP2 - or, alternatively, VFP9 SP2 can simply be run from the slipstream directory (or from a USB device or CD) without the need to install the upgrade.&lt;br&gt;&lt;br&gt;This alleviates the need to run the Community Technology Preview of SP2 on a virtual (or spare) machine in order to still be able to support applications which require VFP9 SP1. &lt;br&gt;&lt;br&gt;To slipstream you need to copy your VFP9 CD to a writeable drive, and make sure the directory is writeable by unchecking Read-Only in it's attributes and selecting the option to cascade this change to subfolders. Then use &lt;a href="http://www.winzip.com"&gt;WinZip &lt;/a&gt;or a similar program to extract the patch file from the SP2 download -&amp;nbsp; in this instance VFP9.0sp2-KB925841-X86-Enu.msp&lt;br&gt;&lt;br&gt;The patch can then be applied using this command (adjusting for file locations as required):&lt;br&gt;&lt;br&gt;&lt;pre&gt;msiexec /a c:\vfoxpro9.0\vs_setup.msi /p c:\temp\VFP9.0sp2-KB925841-X86-Enu.msp&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=3571" width="1" height="1"&gt;</description></item><item><title>Tone</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/09/12/2457.aspx</link><pubDate>Tue, 12 Sep 2006 19:42:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:2457</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/2457.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=2457</wfw:commentRss><description>How applications 'talk' to users, especially in messageboxes and other dialogs, sets the tone for an application and colours the user experience.&lt;br&gt;&lt;br&gt;The first question is usually - who is 'talking' here? The computer, the program, or the developer? Sometimes it's hard to tell. For example &lt;a href="http://img181.imageshack.us/img181/5197/itunesae1.png"&gt;this message&lt;/a&gt; from iTunes/Windows: "iTunes has detected that it is not the default player for audio files." iTunes is referring to itself in the third person, as is the norm. This is one source of the confusion that dialogs can cause - generally users will attempt to determine the source of a popup by the text, rather than the subtle clues that distinguish operating system dialogs from application dialogs, and Windows would refer to iTunes in more or less exactly the same way that iTunes refers to itself. No wonder it causes confusion: but none of the other choices are appealing, either: using the first person - "I've got a problem", or avoiding that by using a passive sense of self - "A problem has occurred" - both fail to identify the program at all, unless visual branding is used, which is often not practical (especially with low-profile applications). Remembering that to users it's their &lt;b&gt;computer&lt;/b&gt; that is trying to communicate with them (rather than a program or programmer) helps to get the tone right.&lt;br&gt;&lt;br&gt;&lt;p&gt;Many of the factors influencing text choices tend to encourage terseness - the desire for a simple interface, allowing that localization can make text longer, or there's never quite enough space allocated by the designer on the dialog to put as much text as you might want. This can lead to a situation where any words deemed as superfluous are excised in order to achieve a clipped quasi-military precision: applications which are overly terse run the risk of appearing to be issuing orders to the user, but excess verbiage must also be a avoided: "Experienced UI designers literally try to minimize the number of words
on dialogs to increase the chances that they will get read." -- &lt;a href="http://www.joelonsoftware.com/uibook/chapters/fog0000000062.html"&gt;Spolsky&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Microsoft's Vista UI guidelines are comprehensive (if not yet complete) and I was interested to see they have put much thought about this basic but crucial part of human-computer interaction and done a whole article on &lt;a href="http://msdn.microsoft.com/library/en-us/UxGuide/UXGuide/Text/TextAndTone.asp?frame=true"&gt;Text And Tone&lt;/a&gt; and using the Vista tone is rule 9 in the Top Rules for Vista applications (where it is defined as "clear, natural, concise, and not overly formal".) These guidelines are primarily intended for consumer applications, but they apply equally to corporate environments where applications often get away with terse, incomprehensible and sometimes outright rude messages because users don't have a choice whether to use them or not. Programs should at the very least be polite and respectful and try to be helpful, and treat users as intelligent beings who might appreciate a little assistance with a task or decision.&lt;/p&gt;
&lt;p&gt;Of course, it's equally important to decide what messages should be presented and how: messageboxes are hopelessly intrusive, and neither Vista's task dialogs or Mac OS's Sheets really solve any of the problems, as they are still modal, still covering your application's user interface (possible even covering the source of or reason for the message), and still much too easy to simply disregard without reading. If a data entry application presents a messagebox that says "Your changes have been saved" &lt;em&gt;every time&lt;/em&gt; the user saves some data, are they really expected to process it every time? I wait hovering over the enter key, ready to tap it as soon as the dialog appears: and if the save goes arse-over-tip and I'm unexpectedly presented with a failure dialog instead, then I'll probably press enter when &lt;em&gt;that&lt;/em&gt; pops up, and I've made an unknown decision. Oops: hope the default action was well chosen.&lt;/p&gt;One common use case of messageboxes is when a problem or 'situation' has occurred and the user has to make a 'decision'. While this may be valid, if a mesage is really important it's worth considering an alternative approach by using your application's own UI to describe the situation and to elicit the user's choice. A &lt;a href="http://en.wikipedia.org/wiki/Modal_window"&gt;common mistake&lt;/a&gt; is to assume that modality simply means being always-on-top, but it doesn't: modality means that the application has different modes, where the same actions have different results. This &lt;a href="http://developer.apple.com/documentation/mac/Toolbox/Toolbox-15.html"&gt;definition&lt;/a&gt; of a modal dialog box reconciles the two, as "a modal dialog box puts the user in the state or 'mode' of being able to work only inside the dialog box." When a modal dialog is open (notably system dialogs like opening or saving a file, or showing a messagebox) the application in a new mode where it's normal functionality is unavailable: there might be different key bindings, no access to the application's inbuilt help (though that can be &lt;a href="/stuartdunkeld/archive/2005/05/05/424.aspx"&gt;accessed from a messagebox in VFP9 using the new BindEvent functionality&lt;/a&gt;) or any application tools. When notifications are handled within the application interface, there is no incentive for the user to immediately dismiss the dialog in order to return to normal application mode, as they have never left it. It's not always practical (or desirable) to do this, but it's worth working to minimise messagebox usage to improve application flow and usability.&lt;br&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There's a list of the &lt;a href="http://msdn.microsoft.com/library/en-us/UxGuide/UXGuide/Principles/TopViolations/TopViolations.asp?frame=true"&gt;Top Violations&lt;/a&gt; of the Vista UI guidelines, which includes many real examples from the current version of Windows. &lt;a href="http://thedailywtf.com/"&gt;The Daily WTF&lt;/a&gt; have some amusing collections of terrible dialogs; although they haven't been updated for a long time the &lt;a href="http://homepage.mac.com/bradster/iarchitect/shame.htm"&gt;User Interface Hall of Shame&lt;/a&gt; (and &lt;a href="http://homepage.mac.com/bradster/iarchitect/fame.htm"&gt;Hall of Fame&lt;/a&gt;) are very instructive. I'm also looking forward also to content appearing in the &lt;a href="http://msdn.microsoft.com/library/en-us/UxGuide/UXGuide/Windows/ErrorMessages/ErrorMessages.asp?frame=true"&gt;Error Message&lt;/a&gt; section of the Vista guidelines.&lt;br&gt;&lt;/p&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=2457" width="1" height="1"&gt;</description></item><item><title>foxkb - foxpro kb article archive</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/08/24/2308.aspx</link><pubDate>Wed, 23 Aug 2006 23:18:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:2308</guid><dc:creator>stuartd</dc:creator><slash:comments>2</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/2308.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=2308</wfw:commentRss><description>&lt;a href="http://www.justkeepswimming.net/foxkb/"&gt;foxkb - foxpro kb article archive&lt;/a&gt;&lt;br&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=2308" width="1" height="1"&gt;</description></item><item><title>Determining Active Directory group membership</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/08/14/2245.aspx</link><pubDate>Mon, 14 Aug 2006 15:19:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:2245</guid><dc:creator>stuartd</dc:creator><slash:comments>2</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/2245.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=2245</wfw:commentRss><description>In order to determine whether a user is a member of an Active Directory group (or a member of a group which is a member of the group) in a large directory, iteration through the members collection is too slow as each group can contain thousands of members: we need to be able to tell what groups are members of each group and recursively query those until we find a group the user is a member of.&lt;br&gt;&lt;br&gt;There are various means of querying the Active Directory: the recommended way for clients like VFP is to use ADO. The ADO AD provider supports two types of query: LDAP filters and SQL. SQL has to be used as VFP doesn't understand the multi-valued adVariant objects returned by LDAP filter queries and can't parse the individual values (using WMI to access the AD returns the same kind of variant arrays, although single-value properties can be accessed)&lt;br&gt;&lt;br&gt;This example code will determine if the current user is a member of a specified Active Directory group:&lt;pre&gt;lparameters cGroup

* Determine if the current user is a member of this group.
* As group memberships can be nested - user is a member of a group
* which is a member of a group which is a member of the target group -
* use recursive queries to look into the contained groups. Because some of the
* groups have thousands of user members, use querying not iteration.

local oConnection, oCommand, oADSysInfo, cUser, oUser, cUserName, ;
	oRoot, cRootDomain, lMember
&lt;br&gt;*	Pass the ADSPath to the target group &lt;br&gt;*	cGroup = "CN=TestGroup,OU=Security Groups,DC=foxite,DC=com"&lt;br&gt;&lt;br&gt;oConnection = createobject("ADODB.Connection")&lt;br&gt;oCommand = createobject("ADODB.Command")&lt;br&gt;&lt;br&gt;oConnection.Provider = "ADsDSOObject"&lt;br&gt;&lt;br&gt;oConnection.open()&lt;br&gt;oCommand.ActiveConnection = oConnection&lt;br&gt;&lt;br&gt;oADSysInfo = createobject("ADSystemInfo")&lt;br&gt;cUser = oADSysInfo.UserName&lt;br&gt;oUser = getobject("LDAP://" + cUser)&lt;br&gt;cUserName = oUser.SamAccountName&lt;br&gt;&lt;br&gt;oRoot = getobject("LDAP://RootDSE")&lt;br&gt;cRootDomain  = oRoot.get("DefaultNamingContext")&lt;br&gt;&lt;br&gt;lMember = .f.&lt;br&gt;&lt;br&gt;IsGroupMember_Recurse(cGroup, cUserName, oCommand, cRootDomain, @lMember)&lt;br&gt;&lt;br&gt;return lMember&lt;br&gt;&lt;br&gt;procedure IsGroupMember_Recurse(cGroup, cUserName, oCommand, cRootDomain, lMember)&lt;br&gt;&lt;br&gt;	if lMember&lt;br&gt;		* We are already a member: speed through the rest of the recursion stack&lt;br&gt;		return&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	* Need a different oRS for each iteration&lt;br&gt;	local oRS, cSQL, cGroupPath&lt;br&gt;&lt;br&gt;	* Is user a member of group?&lt;br&gt;	cSQL = [SELECT name from 'LDAP://] + cRootDomain + ;&lt;br&gt;		[' WHERE memberOf='] + cGroup + ;&lt;br&gt;		[' AND objectClass='user' AND SAMAccountName = '] + cUserName + [']&lt;br&gt;&lt;br&gt;	oCommand.CommandText = cSQL&lt;br&gt;	oRS = oCommand.Execute&lt;br&gt;&lt;br&gt;	if oRS.RecordCount &amp;gt; 0&lt;br&gt;		* ? cUserName + " is a member of " + cGroup&lt;br&gt;		lMember = .t.&lt;br&gt;	else&lt;br&gt;		* Get the groups which are a member of this group&lt;br&gt;		cSQL = [select distinguishedname from 'LDAP://] + cRootDomain + ;&lt;br&gt;			[' where memberof='] + cGroup + [' and objectclass = 'group']&lt;br&gt;&lt;br&gt;		oCommand.CommandText = cSQL&lt;br&gt;		oRS = oCommand.Execute&lt;br&gt;&lt;br&gt;		* Recurse&lt;br&gt;		do while not oRS.eof&lt;br&gt;			cGroupPath = oRS.fields(0).value&lt;br&gt;			IsGroupMember_Recurse(cGroupPath, cUserName, oCommand, cRootDomain, @lMember)&lt;br&gt;			oRS.MoveNext&lt;br&gt;		enddo&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=2245" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1009.aspx">ADSI</category></item><item><title>Calling Vista's TaskDialog API</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/05/23/1570.aspx</link><pubDate>Tue, 23 May 2006 12:29:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:1570</guid><dc:creator>stuartd</dc:creator><slash:comments>5</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/1570.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=1570</wfw:commentRss><description>Calling the TaskDialog API from FoxPro (in Vista build 5308) turned out to be a bit harder than &lt;a href="http://www.sweetpotatosoftware.com/SPSBlog/PermaLink,guid,ce197bae-3878-4ba0-ae6a-d5d39d16e7d5.aspx"&gt;Craig Boyd makes it sound&lt;/a&gt; but once it emerged that the string parameters have to be manually null-terminated and converted to Unicode it became much easier (if you don't convert the strings the output looks like &lt;a href="/stuartdunkeld/attachment/1570.ashx"&gt;this&lt;/a&gt;)&lt;br&gt;&lt;br&gt;
&lt;pre&gt;declare integer TaskDialog in "comctl32.dll" ;
	integer nHWND, ;
	integer hInstance, ;
	string cTitle, ;
	string cDescription, ;
	string cContent, ;
	integer nButtons, ;
	integer nIcon, ;
	integer @ nResult

* Icons

#define TD_ICON_BLANK			100
#define TD_ICON_WARNING			101
#define TD_ICON_QUESTION		102
#define TD_ICON_ERROR			103
#define TD_ICON_INFORMATION		104
#define TD_ICON_BLANK_AGAIN		105
#define TD_ICON_SHIELD			106

* Button values: ABORT and IGNORE appear to have been deprecated.

#define TD_OK            1
#define TD_YES         	 2
#define TD_NO            4
#define TD_CANCEL        8
#define TD_RETRY         16
#define TD_CLOSE         32

cTitle = "Taskdialog title bar text"
cDescription = "Description text"
cContent = "Main content of task dialog text: what to do and " + ;
	"where to do it"

cTitle = strconv(cTitle + chr(0), 5)
cDescription = strconv(cDescription + chr(0), 5)
cContent = strconv(cContent + chr(0), 5)

nButton = 0

nReturn = TaskDialog(_vfp.hwnd, 0, cTitle, cDescription, cContent, ;
	TD_OK + TD_CLOSE, TD_ICON_SHIELD, @nButton)

if nReturn = -2147024809
	? "Invalid arguments were passed"
else
	do case
		case nButton = 1
			? "You pressed OK"
		case nButton = 2
			? "You pressed Cancel"
		case nButton = 4
			? "You pressed Retry"
		case nButton = 6
			? "You pressed Yes"
		case nButton = 7
			? "You pressed No"
		case nButton = 8
			? "You pressed Close"
		otherwise
			? "Return value was " + transform(nButton)
	endcase
endif&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=1570" width="1" height="1"&gt;</description><enclosure url="http://weblogs.foxite.com/stuartdunkeld/attachment/1570.ashx" length="14184" type="image/jpeg" /><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1010.aspx">Vista</category></item><item><title>Connecting to WinRM with Windows Vista</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/05/01/1462.aspx</link><pubDate>Mon, 01 May 2006 20:13:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:1462</guid><dc:creator>stuartd</dc:creator><slash:comments>3</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/1462.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=1462</wfw:commentRss><description>Windows Vista (currently RC1, build 5536) ships with &lt;a href="/stuartdunkeld/archive/2006/03/31/1367.aspx"&gt;WinRM&lt;/a&gt; installed but the service is only started automatically on Longhorn Server: if you want to use WinRM on Vista the service has to be started manually. Whether this is still the case in the final product, or which versions of it, remains to be seen. It's presence currently is no surprise given the shared code base of WS2003 and Vista, but significant changes have been made to the WinRM configuration schema in Vista.&lt;br&gt;&lt;br&gt;The WinRM configuration now includes the settings for WinRS - Windows Remote Shell - and allows these to be set programatically. Also, though, administering WinRM in Vista is now possible via the Group Policy editor.&lt;br&gt;&lt;br&gt;There's also now a &lt;a href="http://windowssdk.msdn.microsoft.com/en-us/library/ms738245.aspx"&gt;quickconfig&lt;/a&gt; 
command which sets WinRM up as a server - starts the service,
creates a listener on all IPs, and adds the firewall exceptions.&amp;nbsp;&lt;pre&gt;&lt;br&gt;&lt;/pre&gt;This it what the revised schema looks like: 
&lt;pre&gt;Config&lt;br&gt;    MaxEnvelopeSizekb = 150&lt;br&gt;    MaxTimeoutms = 60000&lt;br&gt;    MaxBatchItems = 20&lt;br&gt;    SoapTraceEnabled = false&lt;br&gt;    MaxProviderRequests = 25&lt;br&gt;    Client&lt;br&gt;        NetworkDelayms = 5000&lt;br&gt;        URLPrefix = wsman&lt;br&gt;        AllowUnencrypted = false&lt;br&gt;        Auth&lt;br&gt;            Basic = false&lt;br&gt;            Digest = true&lt;br&gt;            Kerberos = true&lt;br&gt;            WindowsIntegratedAuthentication = true&lt;br&gt;        DefaultPorts&lt;br&gt;            HTTP = 80&lt;br&gt;            HTTPS = 443&lt;br&gt;        TrustedHosts&lt;br&gt;    Service&lt;br&gt;        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)S:P(AU;FA;GA;;;WD)(AU;SA;GWGX;;;WD)&lt;br&gt;        MaxConcurrentOperations = 100&lt;br&gt;        EnumerationTimeoutms = 60000&lt;br&gt;        MaxConnections = 5&lt;br&gt;        AllowUnencrypted = false&lt;br&gt;        Auth&lt;br&gt;            Basic = false&lt;br&gt;            Kerberos = true&lt;br&gt;            WindowsIntegratedAuthentication = true&lt;br&gt;        DefaultPorts&lt;br&gt;            HTTP = 80&lt;br&gt;            HTTPS = 443&lt;br&gt;        IPv4Filter = * &lt;br&gt;        IPv6Filter = * &lt;br&gt;    Winrs&lt;br&gt;        AllowRemoteShellAccess = true&lt;br&gt;        IdleTimeout = 4294967295&lt;br&gt;        MaxConcurrentUsers = 5&lt;br&gt;        MaxShellRunTime = 4294967295&lt;br&gt;        MaxProcessesPerShell = 10&lt;br&gt;        MaxMemoryPerShell = 83886080&lt;br&gt;        MaxShellsPerUser = 5&lt;/pre&gt;To test Vista as a client using HTTP, the Basic Auth property must be set to true. To do this, use XML files, the Group Policy editor, or script:&lt;br&gt;
&lt;pre&gt;winrm set winrm/config/client/auth @{Basic="true"}&lt;br&gt;&lt;/pre&gt;

To test Vista as a server using HTTP to connect, basic authentication needs to be enabled for the WinRM service and an HTTP listener created just like for WS2003R2 (although Group Policy has a setting to "Allow automatic configuration of listeners" this does not create the listener itself).&lt;br&gt;&lt;br&gt;The listener uses the newer config URI and introduces some subtle changes from the previous version. The IP selector has been replaced by Address, which has several options: * listens on all IP addresses on the machine,&amp;nbsp; IP:192.168.1.1 listens only on the specified IP address, and MAC:...&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; listens only on IP addresses for the specified MAC.&lt;br&gt;&lt;br&gt;This foxpro code creates, enumrates, and then deletes an HTTP listener which listens on all IP addresses:&lt;br&gt;&lt;pre&gt;* Create, enumerate and destroy a listener - Vista&lt;br&gt;oWsman = createobject("WSMAN.Automation")&lt;br&gt;oSession = oWsman.CreateSession()&lt;br&gt;cSchema = "wsman:microsoft.com/wsman/2005/12/"&lt;br&gt;&lt;br&gt;cComputerName = "VISTA_VM1"&lt;br&gt;cResource = cSchema + "config/Listener?Address=*+Transport=HTTP"&lt;br&gt;&lt;br&gt;cXML = [&amp;lt;cfg:listener xmlns:cfg="] + cSchema + [config/listener.xsd"&amp;gt;]&lt;br&gt;cXML = cXML + "&amp;lt;cfg:hostname&amp;gt;" + cComputerName + "&amp;lt;/cfg:hostname&amp;gt;"&lt;br&gt;cXML = cXML + "&amp;lt;/cfg:listener&amp;gt;"&lt;br&gt;&lt;br&gt;* Create the listener&lt;br&gt;cResponse = oSession.create(cResource, cXML)&lt;br&gt;messagebox(cResponse)&lt;br&gt;&lt;br&gt;* Enumerate it:&lt;br&gt;cResponse = oSession.Enumerate(cSchema + "config/Listener").ReadItem()&lt;br&gt;messagebox(cResponse)&lt;br&gt;&lt;br&gt;* Now delete it: there is no response to this operation if it is successful&lt;br&gt;cResponse = oSession.delete(cResource)&lt;/pre&gt;&lt;br&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=1462" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1008.aspx">WinRM</category><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1010.aspx">Vista</category></item><item><title>Command-line VFP</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/04/27/1456.aspx</link><pubDate>Thu, 27 Apr 2006 09:57:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:1456</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/1456.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=1456</wfw:commentRss><description>It's surprisingly easy to create a command-line interface to your application: example usages could be application updates, maintenance routines, anything that doesn't require a user interface.&lt;br&gt;&lt;br&gt;As an example create a file called vfp.cmd somewhere in the default path (e.g. the system32 directory for local admins or at install) and add the following:
&lt;pre&gt;@cscript //nologo "%~dpn0.vbs" %*&lt;/pre&gt;This executes a script
which is on the same drive, the same path, has the same name as the cmd
file, has the extension vbs, and passes all the command line arguments
to the script host.&lt;br&gt;&lt;br&gt;Then create a file in the same directory as the .cmd file called vfp.vbs with these contents:&lt;pre&gt;dim i, oVFP&lt;br&gt;i = 0&lt;br&gt;&lt;br&gt;wscript.echo "Arguments:"&lt;br&gt;&lt;br&gt;do while i &amp;lt; wscript.arguments.count &lt;br&gt;	wscript.echo wscript.arguments(i) &amp;amp; "(" &amp;amp; i &amp;amp; ")"&lt;br&gt;	i = i + 1&lt;br&gt;loop&lt;br&gt;&lt;br&gt;set oVFP = createobject("VisualFoxpro.Application")&lt;br&gt;wscript.echo "VFP File path:"&lt;br&gt;wscript.echo oVFP.DefaultFilePath&lt;br&gt;&lt;/pre&gt;
Once this has been done, open a command prompt and type
&lt;pre&gt;vfp foo bar &lt;br&gt;&lt;/pre&gt;
to see the results. Your script could process the arguments itself, or it could pass them to your application or COM server depending on the requirements.&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=1456" width="1" height="1"&gt;</description></item><item><title>Remote connections to Windows Server 2003 using WinRM</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/04/10/1419.aspx</link><pubDate>Mon, 10 Apr 2006 21:17:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:1419</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/1419.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=1419</wfw:commentRss><description>&lt;a href="/stuartdunkeld/archive/2006/03/31/1367.aspx"&gt;WinRM&lt;/a&gt; by default only allows remote connections via a secured connection using SSL (HTTPS). It can be configured to allow connections via HTTP, but as &lt;a href="http://technet2.microsoft.com/WindowsServer/en/Library/a733d4e9-923a-4b21-addc-057d947c8fa51033.mspx"&gt;the documentation&lt;/a&gt; says, unless you are using IPSec to secure the connection between your client and server then the credentials used to adminster the server are sent in plaintext.&lt;h4&gt;Configuring client and server (Windows Server 2003)&lt;br&gt;&lt;/h4&gt;This code returns the current configuration for WS2003R2 (see &lt;a href="/stuartdunkeld/archive/2006/03/31/1367.aspx#confgets"&gt;here&lt;/a&gt; for the Vista config schema and what you need to do to access it: I will cover remote connections to/from Vista in another article) To configure WinRM, use the &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/session_put.asp"&gt;Put&lt;/a&gt; method, passing XML returned by the Get on the Config objects with the appropriate attributes set as required. 
&lt;pre&gt;* Access config XML (Win 2003 Server)&lt;br&gt;oWsman = createobject("WSMAN.Automation")
with oWsman.CreateSession()&lt;br&gt;	cXML = .get("wsman:microsoft.com/wsman/2005/06/config")&lt;br&gt;	messagebox(cXML)&lt;br&gt;endwith&lt;br&gt;&lt;/pre&gt;
The default configuration for a Windows 2003 server is below: because the WinRM client and service have basic authentication disabled remote connections via HTTP cannot be attempted or accepted.&lt;br&gt;
&lt;pre&gt;Config
    MaxEnvelopeSizekb = 50
    MaxTimeoutms = 60000
    MaxBatchItems = 20
    SoapTraceEnabled = false
    MaxProviderRequests = 25
    Client
        NetworkDelayms = 5000
        URLPrefix = wsman
        HTTP
            Port = 80
            Unencrypted
                Basic = false
                Digest = false
                Negotiate = false
        HTTPS
            Port = 443
            Basic = true
            Digest = true
            Negotiate = true
    Service
        RootSDDL = O:NSG:BAD:P(A;;GA;;;BA)S:P(AU;FA;GA;;;WD)(AU;SA;GWGX;;;WD)
        MaxConcurrentOperations = 100
        EnumerationTimeoutms = 60000
        MaxClientCertInfoSize = 16384
        MaxConnections = 5
        HTTP
            Unencrypted
                Basic = false
                Negotiate = false
        HTTPS
            Basic = true
            Negotiate = true
&lt;/pre&gt;&lt;h4&gt;Server configuration&lt;br&gt;&lt;/h4&gt;From this output the XML containing the properties to be modified can be extracted and the new values substituted. This example XML could be used to allow basic authentication on inbound HTTP connections in WS2003:&lt;pre&gt;&amp;lt;cfg:Config xmlns:cfg="wsman:microsoft.com/wsman/2005/06/config.xsd"&amp;gt;&lt;br&gt;    &amp;lt;cfg:Service&amp;gt;&lt;br&gt;        &amp;lt;cfg:HTTP&amp;gt;&lt;br&gt;            &amp;lt;cfg:Unencrypted&amp;gt;&lt;br&gt;                &amp;lt;cfg:Basic&amp;gt;true&amp;lt;/cfg:Basic&amp;gt;&lt;br&gt;            &amp;lt;/cfg:Unencrypted&amp;gt;&lt;br&gt;        &amp;lt;/cfg:HTTP&amp;gt;&lt;br&gt;    &amp;lt;/cfg:Service&amp;gt;&lt;br&gt;&amp;lt;/cfg:Config&amp;gt;
&lt;/pre&gt;
&lt;h4&gt;Client configuration&lt;br&gt;&lt;/h4&gt;HTTP authentication has to be explicitly allowed on the client as well: this is for WS2003 as well.&lt;br&gt;
&lt;pre&gt;&amp;lt;cfg:Config xmlns:cfg="wsman:microsoft.com/wsman/2005/06/config.xsd"&amp;gt;&lt;br&gt;    &amp;lt;cfg:Client&amp;gt;&lt;br&gt;        &amp;lt;cfg:HTTP&amp;gt;&lt;br&gt;            &amp;lt;cfg:Unencrypted&amp;gt;&lt;br&gt;                &amp;lt;cfg:Basic&amp;gt;true&amp;lt;/cfg:Basic&amp;gt;&lt;br&gt;            &amp;lt;/cfg:Unencrypted&amp;gt;&lt;br&gt;        &amp;lt;/cfg:HTTP&amp;gt;&lt;br&gt;    &amp;lt;/cfg:Client&amp;gt;&lt;br&gt;&amp;lt;/cfg:Config&amp;gt;

&lt;/pre&gt;To set the configuration, pass the XML via the Put method (to the appropriate config URI). If successful, the new configuration is returned, otherwise XML indicating fault or failure. The session object's Error method will have details of the problem:&lt;br&gt;&lt;pre&gt;cResultXML = oSession.put("wsman:microsoft.com/wsman/2005/06/config", cConfigXML)&lt;br&gt;&lt;/pre&gt;&lt;h4&gt;Creating a Listener&lt;/h4&gt;Once this has been done - or if you intend to connect using HTTPS - one or more listeners must be created on the target system. Listeners must be created and adminstered &lt;i&gt;locally &lt;/i&gt;using WinRM.&lt;br&gt;&lt;br&gt;This example code illustrates creating a listener, enumerating it, and finally deleting it. You will have to specify the IP address and hostname in the appropriate functions:&lt;pre&gt;oWsman = createobject("WSMAN.Automation")&lt;br&gt;oSession = oWsman.CreateSession()&lt;br&gt;cSchema = "wsman:microsoft.com/wsman/2005/06/"&lt;br&gt;&lt;br&gt;cResource = cSchema + "config/Listener?IP="&lt;br&gt;cResource = cResource + GetIPAddress() + "+Port=" + GetPort()&lt;br&gt;&lt;br&gt;cXML = [&amp;lt;cfg:Listener xmlns:cfg="] + cSchema + [config/listener.xsd"&amp;gt;]&lt;br&gt;cXML = cXML + "&amp;lt;cfg:Hostname&amp;gt;" + GetComputerName() + "&amp;lt;/cfg:Hostname&amp;gt;"&lt;br&gt;cXML = cXML + "&amp;lt;cfg:Transport&amp;gt;" + GetTransport() + "&amp;lt;/cfg:Transport&amp;gt;"&lt;br&gt;cXML = cXML + "&amp;lt;/cfg:Listener&amp;gt;"&lt;br&gt;&lt;br&gt;* Create the listener&lt;br&gt;cResponse = oSession.create(cResource, cXML)&lt;br&gt;messagebox(cResponse)&lt;br&gt;&lt;br&gt;* Enumerate it:&lt;br&gt;cResponse = oSession.Enumerate(cSchema + "config/Listener").ReadItem()&lt;br&gt;messagebox(cResponse)&lt;br&gt;&lt;br&gt;* Now delete it: there is no response to this operation if it is successful&lt;br&gt;cResponse = oSession.Delete(cResource)&lt;br&gt;&lt;br&gt;function GetComputerName&lt;br&gt;	return "VMAC"&lt;br&gt;&lt;br&gt;function GetIPAddress&lt;br&gt;	return "192.161.1.240"&lt;br&gt;&lt;br&gt;function GetPort&lt;br&gt;	return "80"&lt;br&gt;&lt;br&gt;function GetTransport&lt;br&gt;	return "HTTP"&lt;br&gt;&lt;/pre&gt;The winrm command line would generally be used to do these operations on servers.&lt;br&gt;&lt;pre&gt;winrm put wsman:microsoft.com/wsman/2005/06/config -file:c:\config.xml
winrm create wsman:microsoft.com/wsman/2005/06/config/Listener?IP=192.168.1.240+Port=80 @{Hostname="VMAC";Transport="http"}&lt;br&gt;winrm enumerate wsman:microsoft.com/wsman/2005/06/config/Listener
&lt;/pre&gt;You can check the connection using a command like the following. It doesn't appear possible to pass the required parameters to the automation session's Get method, as unfortunately the &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/resourcelocator.asp"&gt;resource locator object&lt;/a&gt; is missing in WS2003R2 (it is available in Vista)&lt;pre&gt;winrm get wsman:system/2005/06/this -machine:vmac -transport:http -username:stuart -password:helloworld&lt;/pre&gt;&lt;h4&gt;Establishing the remote connection to WMI&lt;br&gt;&lt;/h4&gt;The WSMan.Automation object has a CreateConnectionsObject method which returns an object to whose properties are assigned the credentials to be used for the connection, and this object is passed to the CreateSession method with the appropriate connection flags. On my home network I have been unable to connect by IP address - the session object's error method returns &amp;lt;h1&amp;gt;Bad Request (Invalid Hostname)&amp;lt;/h1&amp;gt; - and I have been unable to connect through the Windows Firewall.&lt;br&gt;
&lt;pre&gt;#DEFINE WSManFlagCredUsernamePassword	4096
#DEFINE WSManFlagUseDigest	65536	
#DEFINE WSManFlagUseNegotiate	131072	
#DEFINE WSManFlagUseBasic	262144		

oWSMan = createobject("wsman.automation")

oCredentials = oWSMan.CreateConnectionOptions
oCredentials.username = "username"
oCredentials.password = "password"

cAddress = "http://VMAC"

* Use WSManFlagUseNegotiate instead of WSManFlagUseBasic for domain credentials
oSession = oWSMan.CreateSession ;
	(cAddress, WSManFlagUseBasic + WSManFlagCredUsernamePassword, oCredentials)

cSchema =  "http://schemas.microsoft.com/wsman/2005/06/"
cNamespace = "wmi/root/cimv2/"
cClass = "Win32_ComputerSystem"

cResource = cSchema + cNamespace + cClass

oItems = oSession.Enumerate(cResource)
messagebox(oItems.ReadItem())&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=1419" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1008.aspx">WinRM</category></item><item><title>Windows Remote Management (WinRM) part 1</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2006/03/31/1367.aspx</link><pubDate>Thu, 30 Mar 2006 22:47:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:1367</guid><dc:creator>stuartd</dc:creator><slash:comments>1</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/1367.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=1367</wfw:commentRss><description>Windows Server 2003 Release 2 (&lt;a href="http://www.microsoft.com/windowsserver2003/default.mspx"&gt;WS2003R2&lt;/a&gt;) and Windows Vista  introduce a new variety of firewall-friendly remote server management, &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/portal.asp"&gt;Windows Remote Management (WinRM)&lt;/a&gt;, an implementation of the &lt;a href="http://msdn.microsoft.com/library/en-us/dnglobspec/html/wsspecsover.asp"&gt;Web Services for Management&lt;/a&gt; specification. Instead of marshalling objects across RPC using DCOM, WinRM uses SOAP Web Services via HTTP and HTTPS for remote connections. The web server doesn't have to be installed (but it can be) as HTTP is now a &lt;a href="http://justkeepswimming.net/images/http_system_component.jpg"&gt;system component&lt;/a&gt;.&lt;br&gt;&lt;br&gt;It's possible to connect to the WMI service on the remote server for diagnostics and management, or to any hardware &lt;a href="http://en.wikipedia.org/wiki/Baseboard_management_controller"&gt;baseboard management controllers&lt;/a&gt; installed in case of server or component failure. WinRM is installed bud disabled by default in Vista [build 5536] but &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/installation_and_configuration_for_windows_remote_management.asp"&gt;needs to be installed&lt;/a&gt; on WS2003R2. It is not available for Windows XP or earlier versions of Windows.&lt;br&gt;&lt;h4&gt;Accessing resources&lt;br&gt;&lt;/h4&gt;WinRM operations are marshalled by the &lt;a href="http://msdn.microsoft.com/library/en-us/Winrm/winrm/session.asp"&gt;session object&lt;/a&gt; which is returned as the result of a successful &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/wsman_createsession.asp"&gt;connection&lt;/a&gt; to a local or remote computer from the &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/wsman.asp"&gt;WSMan&lt;/a&gt; object.

The session object has a &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/session_get.asp"&gt;Get&lt;/a&gt; method which is used to access a specific class instance - in this case the HTTP system driver.&lt;br&gt;
&lt;pre&gt;local oWsman, oSession, cSchema, cNamespace, ;&lt;br&gt;	cClass, cResource, cResult&lt;br&gt;	&lt;br&gt;oWsman = createobject("WSMAN.Automation")&lt;br&gt;oSession = oWsman.CreateSession()&lt;br&gt;&lt;br&gt;cSchema =  "http://schemas.microsoft.com/wsman/2005/06/"&lt;br&gt;cNamespace = "wmi/root/cimv2/"&lt;br&gt;&lt;br&gt;* Only Key properties can be used to identify the target instance&lt;br&gt;cClass = "Win32_SystemDriver?Name=HTTP"&lt;br&gt;&lt;br&gt;cResource = cSchema + cNamespace + cClass&lt;br&gt;cResult = oSession.Get(cResource)&lt;br&gt;&lt;br&gt;messagebox(DisplayOutput(cResult))&lt;br&gt;&lt;br&gt;procedure DisplayOutput(cXML)&lt;br&gt;	local oXMLFile, oXSLFile&lt;br&gt;	oXMLFile = createobject("MSXml2.DOMDocument.3.0")&lt;br&gt;	oXSLFile = createobject("MSXml2.DOMDocument.3.0")&lt;br&gt;	oXMLFile.loadxml(cXML)&lt;br&gt;	* XSL file is in system32 directory&lt;br&gt;	oXSLFile.load("wsmtxt.xsl")&lt;br&gt;	return oXMLFile.TransformNode(oXSLFile)&lt;br&gt;endproc&lt;/pre&gt;&lt;h4&gt;&lt;a name="confgets"&gt;&lt;/a&gt;Configuration GETs&lt;/h4&gt;There are some specific GET queries which can be executed:
&lt;pre&gt;* Get the WinRM version&lt;br&gt;oSession.Get("wsman:system/2005/06/this")&lt;br&gt;&lt;br&gt;* Get the WinRM configuration (WS2003R2)&lt;br&gt;oSession.Get("wsman:microsoft.com/wsman/2005/06/config")&lt;br&gt;&lt;br&gt;* Get the WinRM configuration (Vista)&lt;br&gt;oSession.Get("wsman:microsoft.com/wsman/2005/12/config")&lt;/pre&gt;Note the Vista configuration URL uses a different schema and requires full Vista administrative rights: to access it you must therefore either be logged on as Administrator or if your account is in the Administrators group you must start VFP via right-clicking on the shortcut or executable file and choosing "Run as administrator" - this attribute can be also set as the default on a given shortcut in the "Compatibility" tab.&lt;br&gt;&lt;h4&gt;Simple enumerations&lt;/h4&gt;The session object's &lt;a href="http://msdn.microsoft.com/library/en-us/winrm/winrm/session_enumerate.asp"&gt;Enumerate&lt;/a&gt; method returns a collection of objects of a certain class and allows iteration through them.
&lt;pre&gt;local oWsman, oSession, cSchema, cNamespace, ;&lt;br&gt;	cClass, cResource, cXML, oItems&lt;br&gt;	&lt;br&gt;oWsman = createobject("WSMAN.Automation")&lt;br&gt;oSession = oWsman.CreateSession()&lt;br&gt;&lt;br&gt;cSchema =  "http://schemas.microsoft.com/wsman/2005/06/"&lt;br&gt;cNamespace = "wmi/root/cimv2/"&lt;br&gt;cClass = "Win32_DiskDrivePhysicalMedia"&lt;br&gt;cResource = cSchema + cNamespace + cClass&lt;br&gt;&lt;br&gt;oItems = oSession.Enumerate(cResource)&lt;br&gt;&lt;br&gt;do while oItems.AtEndOfStream = .f.&lt;br&gt;	cXML = oItems.ReadItem()&lt;br&gt;	* View output as formatted XML this time.&lt;br&gt;	messagebox(DisplayOutputXML(cXML))&lt;br&gt;enddo&lt;br&gt;&lt;br&gt;procedure DisplayOutputXML(cXML)&lt;br&gt;	local oXMLFile, oXSLFile&lt;br&gt;	oXMLFile = createobject("MSXml2.DOMDocument.3.0")&lt;br&gt;	oXSLFile = createobject("MSXml2.DOMDocument.3.0")&lt;br&gt;	oXMLFile.loadxml(cXML)&lt;br&gt;	* wsmpty.xsl outputs formatted XML&lt;br&gt;	oXSLFile.load("wsmpty.xsl")&lt;br&gt;	return oXMLFile.TransformNode(oXSLFile)&lt;br&gt;endproc&lt;br&gt;
&lt;/pre&gt;&lt;h4&gt;WQL queries in WinRM&lt;/h4&gt;In WS2003R2, this is as far as enumerate goes - you get all the properties of all the objects. Vista, though, has the capacity to process WQL queries:&lt;br&gt;
&lt;pre&gt;local oWsman, oSession, cDialect, cFilter, ;&lt;br&gt;	oResultSet, cResource, cXML&lt;br&gt;&lt;br&gt;oWsman = createobject("Wsman.Automation")&lt;br&gt;oSession = oWsman.CreateSession()&lt;br&gt;&lt;br&gt;* Wildcard terminates the resource string&lt;br&gt;cResource = "http://schemas.microsoft.com/wsman/2005/06/wmi/root/cimv2/*"&lt;br&gt;cDialect = "http://schemas.microsoft.com/wsman/2005/06/WQL"&lt;br&gt;&lt;br&gt;* Example queries:&lt;br&gt;* cFilter = [select EventCode from Win32_NTLogEvent where SourceName="WINRM"]&lt;br&gt;* cFilter = [select Name from Win32_Service where StartType="Disabled"]&lt;br&gt;cFilter = "select AdapterType,MACAddress from Win32_NetworkAdapter where PhysicalAdapter=True"&lt;br&gt;&lt;br&gt;oResultSet = oSession.Enumerate(cResource, cFilter, cDialect)&lt;br&gt;&lt;br&gt;do while not oResultSet.AtEndOfStream&lt;br&gt;	cXML = oResultSet.ReadItem&lt;br&gt;	DisplayProperties(cXML)&lt;br&gt;enddo&lt;br&gt;&lt;br&gt;* Parse the XML manually this time&lt;br&gt;procedure DisplayProperties&lt;br&gt;&lt;br&gt;	lparameters cXML&lt;br&gt;	local oParser, oNodes, cProperty, cValue, oItem&lt;br&gt;&lt;br&gt;	oParser = createobject("MSXml2.DOMDocument.3.0")&lt;br&gt;	oParser.loadxml(cXML)&lt;br&gt;	oNodes = oParser.firstChild.firstChild.ChildNodes&lt;br&gt;&lt;br&gt;	for each oItem in oNodes&lt;br&gt;		cProperty = oItem.basename&lt;br&gt;		cValue = oItem.nodetypedvalue&lt;br&gt;		? cProperty + ": " + cValue&lt;br&gt;	next&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;/pre&gt;
&lt;h4&gt;Calling methods&lt;/h4&gt;
Methods - static and non-static - are called using the session object's Invoke method: input parameters and output parameters must be serialized as XML. This particular method - Win32_Process.Create - doesn't work in WinRM/Vista build 5308.&lt;br&gt;
&lt;pre&gt;local oWSMan, oSession, cClass, cSchema, cNamespace, cResource, ;&lt;br&gt;	cMethod, cInputParameters, cOutputParameters&lt;br&gt;&lt;br&gt;oWSMan = createobject("WSMAN.Automation")&lt;br&gt;oSession = oWSMan.CreateSession()&lt;br&gt;&lt;br&gt;cSchema =  "http://schemas.microsoft.com/wsman/2005/06/"&lt;br&gt;cNamespace = "wmi/root/cimv2/"&lt;br&gt;&lt;br&gt;* Create is a static method so obtain a generic class instance&lt;br&gt;* rather than a specific instance.&lt;br&gt;&lt;br&gt;cClass = "WIN32_Process"&lt;br&gt;cMethod = "Create"&lt;br&gt;&lt;br&gt;cResource = cSchema + cNamespace + cClass&lt;br&gt;&lt;br&gt;* Create Input Parameter XML. &lt;br&gt;cInputParameters = "&amp;lt;p:" + cMethod + "_INPUT "&lt;br&gt;cInputParameters = cInputParameters + 	[xmlns:p="] + cSchema + cNamespace + ["] + "&amp;gt;"&lt;br&gt;&lt;br&gt;* Multiple parameters can be sent, but not embedded objects &lt;br&gt;* (so the WIN32_ProcessStartup object cannot be sent)&lt;br&gt;cInputParameters = cInputParameters + GetParameterXML("CommandLine", "calc.exe")&lt;br&gt;cInputParameters = cInputParameters + GetParameterXML("CurrentDirectory", "c:\")&lt;br&gt;&lt;br&gt;cInputParameters = cInputParameters + "&amp;lt;" + "/p:" + cMethod + "_INPUT" + "&amp;gt;"&lt;br&gt;&lt;br&gt;cOutputParameters = oSession.Invoke(cMethod, cResource, cInputParameters)&lt;br&gt;&lt;br&gt;* Extract the values directly from the XML using STREXTRACT (VFP7 or later)&lt;br&gt;cReturnValue = strextract(cOutputParameters, "&amp;lt;p:ReturnValue&amp;gt;", "&amp;lt;/p:ReturnValue&amp;gt;")&lt;br&gt;cProcessID = strextract(cOutputParameters, "&amp;lt;p:ProcessID&amp;gt;", "&amp;lt;/p:ProcessID&amp;gt;")&lt;br&gt;&lt;br&gt;if cReturnValue = "0"&lt;br&gt;	cMessage = "Process created with an ID of " + cProcessID&lt;br&gt;else&lt;br&gt;	cMessage = "Process creation failed: return value was " + cReturnValue&lt;br&gt;endif&lt;br&gt;&lt;br&gt;messagebox(cMessage)&lt;br&gt;&lt;br&gt;procedure GetParameterXML&lt;br&gt;	lparameters cParameterName, cParameterValue&lt;br&gt;	cXML = "&amp;lt;p:" + cParameterName + "&amp;gt;" + cParameterValue + ;&lt;br&gt;		"&amp;lt;/p:" + cParameterName + "&amp;gt;"&lt;br&gt;	return cXML&lt;br&gt;endproc&lt;br&gt;&lt;/pre&gt;In part2 I will show how to make remote connections and to configure WinRM to allow connections over HTTP.&lt;br&gt;&lt;pre&gt;&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=1367" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1007.aspx">WMI</category><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1008.aspx">WinRM</category><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1010.aspx">Vista</category></item><item><title>WMI part 4 - Implementing a temporary WMI Event consumer in VFP</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2005/09/16/915.aspx</link><pubDate>Fri, 16 Sep 2005 05:21:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:915</guid><dc:creator>stuartd</dc:creator><slash:comments>2</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/915.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=915</wfw:commentRss><description>All the WMI queries so far in this series have been looking at static data, but WMI has a mechanism for checking for changes to local or remote computers and reacting to them: WMI events. Applications can register themselves as event consumers, and receive notifications of these changes. This article shows how, in VFP6 or later.
&lt;h4&gt;Extrinsic Events and Semi-Synchronous Queries&lt;/h4&gt;
There are two main types of WMI events. The simpler ones are the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/__extrinsicevent.asp"&gt;extrinsic event&lt;/a&gt; classes. These report on events outside WMI's domain, and consequently have to have an event provider, which alerts WMI when the event takes place. Windows XP and 2003 introduce lots of fun new classes and event providers but only those available in Windows 2000 are used in this article (many are also available in Win 9x and/or NT). The &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/power_management_event_provider.asp"&gt;power management event provider&lt;/a&gt; (2000+) and the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/registry_event_classes.asp"&gt;registry event provider&lt;/a&gt; are the two main extrinsic event classes available across the spectrum of Windows versions.&lt;br&gt;&lt;br&gt;Using a method of the WMI service object - &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/swbemservices_execnotificationquery.asp"&gt;ExecNotificationQuery&lt;/a&gt; - WMI is instructed to go away and wait for something to happen with our selected event class, and to come back and tell us when it does.&lt;br&gt;&lt;br&gt;
WMI event sample code usually uses this semi-synchronous method for event queries: the obvious attraction is that the syntax is compellingly simple. The downside (in a single-threaded environment like Foxpro at least) is that it blocks the calling application until the event is received, the call reaches it's (optional) timeout, or the application is forcibly terminated: whichever is the earliest. Running the following code sample completely blocks Foxpro for as many milliseconds are specified as the timeout, unless a power management event occurs in that time (you will need a laptop running Windows 2000 or later to generate an actual power management event.)
&lt;pre&gt;** Example of semi-synchronously querying an extrinsic event
** If you can't generate a power event, you need to wait for the timeout.

oWMILocator = createobject("WbemScripting.SWbemLocator")
oWMI = oWMILocator.ConnectServer(".", "root\cimv2")

cQuery = "select * from WIN32_PowerManagementEvent"
oResults = oWMI.ExecNotificationQuery(cQuery)
* Timeout in milliseconds...
nTimeout = 10000
oEvent = oResults.NextEvent(nTimeout)
* ... 12289 Error results if no event occurs within the timeout.
do case
	case oEvent.EventType = 4
		? "Entering Suspend"
	case oEvent.EventType = 7
		? "Resume from Suspend"
	case oEvent.EventType = 10
		? "Power Status Change" &amp;amp;&amp;amp; mains &amp;lt;-&amp;gt; battery
	case oEvent.EventType = 11
		?  "OEM Event"
	case oEvent.EventType = 18
		? "Resume Automatic"
endcase
&lt;/pre&gt;
&lt;br&gt;
&lt;h4&gt;Asynchronous Queries and Sink Objects&lt;/h4&gt;
Blocking behaviour like this effectively useless, so it's just as well that there is another way - as long as we are querying our local computer, we can use &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/invoking_an_asynchronous_query.asp"&gt;asynchronous queries&lt;/a&gt; instead. Asynchronous calls to other computers are &lt;strong&gt;strongly&lt;/strong&gt; discouraged due to &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/setting_security_on_an_asynchronous_call.asp"&gt;security considerations&lt;/a&gt; and may fail because of them. There is a workaround for semi-synchronous queries, unsurprisingly involving worker threads, which I will come to later.&lt;br&gt;&lt;br&gt;
The &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/swbemservices_execnotificationqueryasync.asp"&gt;ExecNotificationQueryAsync&lt;/a&gt; method is used for asynchronous event queries. There are two required parameters: a notification query to execute, and a WMI sink object to receive the event notification from WMI. WMI provides the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/swbemsink.asp"&gt;sink class&lt;/a&gt;, all we need to do from VFP is create a VFP sink object and bind it to the WMI sink's events. In VFP7 and later, the intrinsic VFP EventHandler function can be used, but for VFP6 the VFPCom utility is required. The &lt;a&gt;latest version&lt;/a&gt; of VFPCom works with VFP6: you'll see in the code later that the sink class syntax differs slightly when using VFPCOM to bind the two objects together.&lt;br&gt;&lt;br&gt;
The WMI sink's OnObjectReady event fires when the notification query finds a hit, and receives the class we are monitoring. OnComplete fires when the query ends, or is cancelled (the WMI sink object has a Cancel method which is used to cancel all asynchronous queries bound to it).
&lt;h4&gt;Intrinsic Events&lt;/h4&gt;
The second type of WMI events, intrinsic events, allow monitoring any of the WMI classes (representing &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/computer_system_hardware_classes.asp"&gt;hardware&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/operating_system_classes.asp"&gt;Windows components&lt;/a&gt;, &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/installed_applications_classes.asp"&gt;programs&lt;/a&gt;, etc) and raise an event as required when an instance of the target class is 'created', 'modified', or 'deleted' - the precise nature of the actual event that generates this instance depends on the class context. The query syntax is a bit more complex for this type of events.&lt;br&gt;&lt;br&gt;
As an example, a simple query would monitor for a new process (i.e. program) being started on the computer. In the query, we need to request instances of a WMI system class, in this case &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/__instancecreationevent.asp"&gt;__InstanceCreationEvent&lt;/a&gt;, to generate the event when a new instance of the process class is created by a program being started. We also need to specify a polling interval for the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/within_clause.asp"&gt;WITHIN&lt;/a&gt; clause, which is mandatory for event queries which don't have an event provider (an error is raised if it's omitted). The interval is the maximum amount of time in seconds that can elapse before notification of an event must be delivered: the right value to use depends on what you are monitoring. Higher values will receive notifications less frequently, low values (less than 1) affect system and application performance and may eventually generate an error.&lt;br&gt;&lt;br&gt;
Intrinsic event queries must have a &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/where_clause.asp"&gt;WHERE&lt;/a&gt; clause: at the least, the event class's TargetInstance property must be the target class name as specified by the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/isa_operator_for_event_queries.asp"&gt;ISA&lt;/a&gt; operator (instances of all subclasses of the specified class are monitored). A more specific where clause will result in better performance: it must follow the "property operator value" syntax throughout though. Note that the where clause cannot reference any array properties the class may have.&lt;br&gt;&lt;br&gt;
Put these pieces together and this valid query is the result: it checks every three seconds for a new process being started.
&lt;pre&gt;select * from __InstanceCreationEvent within 3 where TargetInstance ISA 'Win32_process'
&lt;/pre&gt;
Now we can call the ExecNotificationQueryAsync with the WMI sink object and query. Whan a new process is created, the sink object's OnObjectReady event fires, the instance creation object is received, and has a reference to the new object in it's TargetInstance property. Note that WMI will create a process of it's own - unsecapp.exe - to handle the callback from the asynchronous method call.
&lt;br&gt;&lt;br&gt;
&lt;h4&gt;Event catching class&lt;/h4&gt;
If either sink variable goes out of scope, they are automatically unbound, so it's useful to encapsulate them in a class. This sample code uses a general event catching class to watch for new processes being started. There are two versions, one suitable for use in VFP7 or later and one for use in VFP6 or (possibly) earlier which requires the VFPCom utility from Microsoft. Subsequent examples will only show the newer sink syntax.
&lt;pre&gt;** Demo query: when a new process is started, echo it's WMI path to the screen.
* RELEASE oEventCatcher in the command window when you tire of receiving events.

public oEventCatcher
* Change class to eventcatcher6 for VFP6 version
oEventCatcher = createobject("eventcatcher")

cQuery = [select * from __InstanceCreationEvent within 3 where TargetInstance ISA 'Win32_process']
oEventCatcher.CatchEvents(cQuery)

return

**
** Event catching class
**

* VFP7+ version

define class eventcatcher as relation

	oSink = .null.
	oWbemSink = .null.
	oWMI = .null.

	procedure init

		this.oWMI = getobject("winmgmts:")
		* Create the sink objects and bind them
		this.oWbemSink = createobject("wbemscripting.swbemsink")
		this.oSink = createobject("vfpsink")
		eventhandler(this.oWbemSink, this.oSink)

	endproc

	procedure destroy

		if vartype(this.oWbemSink) = "O"
			this.oWbemSink.Cancel()
		endif

	endproc

	procedure CatchEvents
		lparameters cQuery
		* Send the WMI sink not the VFP one.
		this.oWMI.ExecNotificationQueryAsync(this.oWbemSink, cQuery)
	endproc

enddefine

** The sink class is easier to manage when decoupled from the eventcatcher class

define class vfpsink as relation

	implements ISWbemSinkEvents in "WbemScripting.SWbemSink"

	procedure ISWbemSinkEvents_OnObjectReady(oObject, oAsyncContext)
		* Assign the TargetInstance object reference to a property at this point.
		? oObject.TargetInstance.Path_.Path
	endproc

	procedure ISWbemSinkEvents_OnCompleted(nResult, oErrorObject, oAsyncContext)
	procedure ISWbemSinkEvents_OnProgress(nUpperBound, nCurrent, cMessage, oAsyncContext)
	procedure ISWbemSinkEvents_OnObjectPut(oObjectPath, oAsyncContext)

enddefine

** VFP6- version

define class eventcatcher6 as relation

	oSink = null
	oWbemSink = null
	oWMI = null
	oVFPCom = null

	procedure init

		oWMILocator = createobject("WbemScripting.SWbemLocator")
		this.oWMI = oWMILocator.ConnectServer(".", "root\cimv2")
		* Create the sink objects and bind them
		this.oWbemSink = createobject("wbemscripting.swbemsink")
		this.oSink = createobject("vfpsink6")
		this.oVFPCom = createobject("vfpcom.comutil")
		this.oVFPCom.BindEvents(this.oWbemSink, this.oSink)

	endproc

	procedure destroy

		if vartype(this.oWbemSink) = "O"
			this.oWbemSink.Cancel()
		endif

	endproc

	procedure CatchEvents
		lparameters cQuery
		this.oWMI.ExecNotificationQueryAsync(this.oWbemSink, cQuery)
	endproc
enddefine


define class vfpsink6 as relation

	procedure OnObjectReady
		lparameters oObject, oAsyncContext
		? oObject.TargetInstance.Path_.Path
	endproc

	procedure OnCompleted
		lparameters nResult, oErrorObject, oAsyncContext
	procedure OnProgress
		lparameters nUpperBound, nCurrent, cMessage, oAsyncContext
	procedure OnObjectPut
		lparameters oObjectPath, oAsyncContext

enddefine
&lt;/pre&gt;

&lt;h4&gt;Extending the event catcher&lt;/h4&gt;
One scenario an application might have to deal with is starting a process, and waiting for it to terminate. We can do this using the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/__instancedeletionevent.asp"&gt;__InstanceDeletionEvent&lt;/a&gt; class. This still receives a TargetInstance reference (it is to a copy of the deleted object.)
&lt;pre&gt;public oEventCatcher
oWMILocator = createobject("WbemScripting.SWbemLocator")
oWMI = oWMILocator.ConnectServer(".", "root\cimv2")
oEventCatcher = createobject("eventcatcher")
oProcessClass = oWMI.Get("Win32_Process")

* Can't just call the static Create method as we won't get a reference 
* to the new process: another approach is needed:
* use the OutParameters object's Process ID property 

* First create the InParameters object:
oInParameters = oProcessClass.Methods_("Create").InParameters.SpawnInstance_

* Set the required command line property for the process to be monitored:
oInParameters.CommandLine = "notepad.exe"

* Now call the method using ExecMethod which returns the OutParameters object:
oOutParameters = oWMI.ExecMethod("WIN32_process", "Create", oInParameters)

* Now we can construct the event query which will fire when this
* specific process ends... no idea what's with Windows 9x here,
* but the (-ve) process ID needs tweaking, at least under Virtual PC.

cQuery = "select * from __InstanceDeletionEvent within 1 "
cQuery = cQuery + " where TargetInstance ISA 'Win32_Process' and TargetInstance.ProcessID="
if os() = "Windows 4"
	nProcessID = 4294967296 + oOutParameters.processID
else
	nProcessID = oOutParameters.processID 
endif

cQuery = cQuery + str(nProcessID)
? cQuery

* Execute the query.
oEventCatcher.CatchEvents(cQuery)
&lt;/pre&gt;
There is one more intrinsic event class, &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/__instancemodificationevent.asp"&gt;__InstanceModificationEvent&lt;/a&gt;: this adds a PreviousInstance property, which has a reference to the object before it was modified. This is very useful for detecting specific changes: the where clause must still follows the property-operator-value syntax though: a query containing "where TargetInstance.Property &amp;lt;&amp;gt; PreviousInstance.Property" will result in an Unparsable Query error.&lt;br&gt;&lt;br&gt;This query watches for a disk being inserted into a CD drive:
&lt;pre&gt;select * from __InstanceModificationEvent within 1 where TargetInstance ISA 'Win32_CDROMDrive' 
and TargetInstance.MediaLoaded = True and PreviousInstance.MediaLoaded = False 
&lt;/pre&gt;
Another example watches installed printers for a change of orientation:
&lt;pre&gt;select * from __InstanceModificationEvent within 1 where TargetInstance ISA 'Win32_PrinterConfiguration' 
and (TargetInstance.Orientation = 1 and PreviousInstance.Orientation = 2
or TargetInstance.Orientation = 2 and PreviousInstance.Orientation = 1)
&lt;/pre&gt;
What you look for with a WMI event query depends on your application's needs: the extrinsic power management query we used at the beginning of the article will work asynchronously (the receiving sink class would have to change as it will receive the Win32_PowerManagementEvent class instead of one of the intrinsic event classes, so would not then need to reference TargetInstance.) An application could augment that by directly monitoring a laptop's power status by looking at the BatteryStatus property of the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/win32_portablebattery.asp"&gt;Win32_PortableBattery&lt;/a&gt; class.. This is probably achievable using &lt;a href="http://msdn.microsoft.com/library/en-us/dv_foxhelp9/html/e0aaf535-c606-44bd-b7c1-7d0341331d8f.asp"&gt;BindEvent in VFP9&lt;/a&gt;, but not at all easy to achieve otherwise in earlier versions of VFP.&lt;br&gt;&lt;br&gt;
All the notification classes derive from &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/__instanceoperationevent.asp"&gt;__InstanceOperationEvent&lt;/a&gt; - in order to query more than one type of operation we can request instances of this class. The query will raise events for all three subclasses, and the sink can determine which is which by the object's Path_.Class property. There's an example of this later on.
&lt;h4&gt;Using the event catcher to catch Event Log events&lt;/h4&gt;
It's easy to use this technique to monitor for specific events in the Windows Event Log:
&lt;pre&gt;* See all events:
select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent'

* Catch only specific events: 4202 is a network transport failure
select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent'
and TargetInstance.EventCode=4202

* Catch only events from a specific source: in this case WMI itself.
select * from __InstanceCreationEvent where TargetInstance ISA 'Win32_NTLogEvent'
and TargetInstance.SourceName='WinMgmt'
&lt;/pre&gt;
This will raise an error on a local query if the query results include a log event from the Security Event Log. Access to the Security log requires a WMI privilege to be added to the connection: this should be done at the time of the call in Windows 2000+ but must be requested as part of the original connection in Windows 95/NT. See my &lt;a href="/stuartdunkeld/archive/2005/09/14/910.aspx"&gt;WMI connection class&lt;/a&gt; for how to do that.
&lt;pre&gt;define class logeventcatcher as eventcatcher

	procedure CatchEvents
		lparameters cQuery
		* Required to access the Security log
		this.oWMI.Security_.Privileges.AddAsString("SeSecurityPrivilege")
		dodefault(cQuery)
	endproc

enddefine
&lt;/pre&gt;
If you look at the properties of a Log Event, you'll see that several use the WMI time format - e.g. 20050905151317.000000+060. In order to easily transform these into VFP datetime values you can use this format code:
&lt;pre&gt;cWMIDate = oEvent.TimeGenerated
tDateTime = ctot(transform(cWMIDate, '@R 9999-99-99T99:99:9999'))
&lt;/pre&gt;
The final component of the WMI datetime (+060 in the example above) indicates the offset in minutes from GMT. (There's a &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/swbemdatetime.asp"&gt;scripting object&lt;/a&gt; which works with WMI dates but it's only available in Windows XP/2003.)&lt;br&gt;&lt;br&gt;
Incidentally, the upper case "T" in the format mask is used to tell CTOT() that it should internally switch to YMD format for the date conversion regardless of what the SET DATE settings are currently.
&lt;h4&gt;Querying Association Class Events&lt;/h4&gt;
Association classes are the glue classes that tie the WMI class hierarchy together. An association class represents a relationship between objects - &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/win32_userdesktop.asp"&gt;Win32_UserDesktop&lt;/a&gt; relates users and their desktop settings: if you have one, you can determine the other. Other association classes represent the relationship between a single entity and a group - an instance of one of these classes has a property for the "owner" object and a collection representing the "owned" objects. &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/cim_processexecutable.asp"&gt;CIM_ProcessExecutable&lt;/a&gt; links processes and the files they have open, for example.
&lt;br&gt;&lt;br&gt;
This sample association class query watches a specific directory - in this case c:\temp - for files being created, changed, or deleted. The query includes both single and double quotes, and note the amount of escaping required on the backslashes. This will work for mapped network drives but not UNC paths.
&lt;pre&gt;select * from __InstanceOperationEvent within 3 where 
TargetInstance ISA 'CIM_DirectoryContainsFile' and
TargetInstance.GroupComponent='WIN32_Directory.name="c:\\\\temp"'
&lt;/pre&gt;
We need to change the sink class for this query as the TargetInstance is an association class: association classes have a number of ways to refer to their components: in this case, GroupComponent and PartComponent are the association properties, but other property pairs used include Antecedent/Dependent, and Element/Setting.  The difference from our point of view here is that we don't receive a reference to an instance of the class in the association class, we instead get the full WMI path. If the object still exists, then the WMI Get() method can be used to get an object reference, but that's not the case for __InstanceDeletion as is shown here:
&lt;pre&gt;procedure ISWbemSinkEvents_OnObjectReady(oObject, oAsyncContext)

	* Scratch WMI object
	oWMILocator = createobject("WbemScripting.SWbemLocator")
	oWMI = oWMILocator.ConnectServer(".", "root\cimv2")

	do case
		case oObject.Path_.class = "__InstanceCreationEvent"
			oFile = oWMI.get(oObject.TargetInstance.PartComponent)
			? "File " + oFile.name + " was created with filesize " + oFile.FileSize

		case oObject.Path_.class = "__InstanceModificationEvent"
			* This never fires for file modifications. Renaming a file produces a
			* deletion event and a creation event instead.
			? "This never fires for files"

		case oObject.Path_.class = "__InstanceDeletionEvent"
			* Can't "get" an object that's been deleted.
			? oObject.TargetInstance.PartComponent + " was deleted"
	endcase

endproc

&lt;/pre&gt;

&lt;h4&gt;A solution for remote semi-synchronous queries&lt;/h4&gt;

We started this by looking at the semi-synchronous event query ExecNotificationQuery, which blocked Foxpro while waiting for the event. Asynchronous queries are a solution for local queries, but are strongly discouraged for use with &lt;a href="/stuartdunkeld/archive/2005/09/14/910.aspx"&gt;remote queries&lt;/a&gt;. 
We can use semi-synchronous remote queries if we can create a worker thread for each, though: fortunately, there is a &lt;a href="http://www.geocities.com/siliconvalley/hills/9119/multithreaded_vfp.htm"&gt;3rd party COM component&lt;/a&gt; that enables VFP applications to launch background tasks in separate threads, and is free to use and distribute according to the accompanying license (it requires MSVCP70.DLL too).&lt;br&gt;&lt;br&gt;
To see it in action, create a project and compile a MTDLL with this program as the main: save it as wmimtdll.dll (if you prefer to use another name the name will have to be changed in the call below)
&lt;pre&gt;define class eventcatcher as session olepublic

	procedure init
	procedure destroy

	procedure CatchEvents
		lparameters cQuery, cServer, cNamespace &amp;amp;&amp;amp; cUsername, cPassword etc
		oLocator = createobject("wbemscripting.swbemlocator")
		* See http://weblogs.foxite.com/stuartdunkeld/archive/2005/09/14/910.aspx
		* for username syntax or use the WMI Connection class.
		oWMI = oLocator.ConnectServer(cServer, cNamespace, "", "", "", "", 128)
		oResults = oWMI.ExecNotificationQuery(cQuery)
		oEvent = oResults.NextEvent()
		return oEvent
	endproc

	procedure error(nError, cMethod, nLine)
		comreturnerror("MTEventCatcher", "Error " + transform(nError) + " at line " + transform(nLine) + " of " + cMethod)
	endproc

enddefine
&lt;/pre&gt;
We still need a sink to receive the event: we make our own this time. There's a version for VFP6 too. 
&lt;pre&gt;define class mtsink as relation

	implements _IWorkerEvents in "vfpmtapp.worker"

	procedure _IWorkerEvents_OnTerminate
		lparameters vResult
		? vResult.TargetInstance.Name
	endproc

enddefine

define class mtsink6 as relation

	procedure OnTerminate
		lparameters vResult
		? vResult.TargetInstance.Name
	endproc

enddefine
&lt;/pre&gt;
It won't come as a blinding surprise that now we create the various objects and bind them together. Note we pass the thread factory an array containing the parameters to be passed to the VFP object method call. This will only work in the command window unless the variables are kept in scope.
&lt;pre&gt;* This is the 3rd party component
oThreadFactory = createobject("vfpmtapp.worker.1")

* VFP7: create the sink and bind it.
oSink = createobject("mtsink")
eventhandler(oThreadFactory, oSink)

* VFP6
* oSink = createobject("mtsink6")
* oVFPCom = createobject("vfpcom.comutil")
* oVFPCom.BindEvents(oThreadFactory, oSink)

cQuery = "select * from __InstanceCreationEvent within 1 where TargetInstance ISA 'WIN32_Process'" 
cServer = "remote_computer" &amp;amp;&amp;amp; this will still work locally.

dimension aParameters[3]
aParameters[1] = cQuery
aParameters[2] = cServer
aParameters[3] = "root\CIMV2"
* Change this call if you chose another name for the DLL:
oThreadFactory.Run("wmimtdll.eventcatcher", "CatchEvents", @aParameters)
&lt;/pre&gt;
There are limitations to using this control: as the author states "If you return an object ... do not cache a reference to it, the object will be destroyed after the the last notification is executed, because the working thread that hosts this object will end". This means that when the sink's OnTerminate method finishes, the object reference disappears so all the object processing needs to take place in the sink (which can call other methods). Another issue (with WMI more than with the control) is that after NextEvent has returned an event, the thread ends and the query has to be reinvoked.&lt;br&gt;&lt;br&gt;Providing worker threads sounds like something Sedna could do: until then, this will do, especially in a scenario where one computer is querying many remote computers and there are no distribution issues to worry about.
&lt;h4&gt;Temporary and Permanent event consumers&lt;/h4&gt;
Thus far we have looked at the events that applications or their users might be interested in: the techniques used only allow events to be caught while an application or script is running. There are numerous events that it would be very useful to monitor all the time, whether an application is running or not, and especially on a server: checking for free diskspace of less than 10% on any disk, or CPU usage stuck at 100%, memory usage going through the roof, unknown processes or services, &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/win32_uninterruptiblepowersupply.asp"&gt;UPS&lt;/a&gt; status - WMI has an inbuilt mechanism for this kind of event query. If you create a Permanent Event Consumer (a COM object implementing the &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/iwbemunboundobjectsink.asp"&gt;IWbemUnboundObjectSink&lt;/a&gt; interface) linked to an EventFilter query, then when the event fires, WMI will instantiate the object and pass it the event data.&lt;br&gt;&lt;br&gt;Edit: this isn't possible in VFP, whose implementation of IMPLEMENTS is lacking the capability of returning the interface when requested via&amp;nbsp; &lt;a href="http://msdn.microsoft.com/library/en-us/com/html/65e758ce-50a4-49e8-b3b2-0cd148d2781a.asp?frame=true"&gt;CoGetClassObject&lt;/a&gt;. The error hresult is 0x80040154, CLASS_NOT_REGISTERED.&lt;br&gt;&lt;br&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=915" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1007.aspx">WMI</category></item><item><title>WMI part 3 - Remote Queries</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2005/09/14/910.aspx</link><pubDate>Wed, 14 Sep 2005 21:49:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:910</guid><dc:creator>stuartd</dc:creator><slash:comments>4</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/910.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=910</wfw:commentRss><description>One of the principles of WMI is that everything that can be queried or set locally should be able to be done remotely.&lt;blockquote&gt;WMI makes no distinction between local and remote access ... The difference between a local and a remote connection is that users can specify a user name and password in a remote connection, replacing the current user name and password. With a local connection, users cannot override the current name and password."&lt;br /&gt;&lt;span&gt;&lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/connecting_to_wmi_on_a_remote_computer.asp"&gt;Connecting to WMI on a remote computer&lt;/a&gt;&lt;/span&gt;&lt;/blockquote&gt;With &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/creating_processes_remotely.asp"&gt;the odd exception&lt;/a&gt; this is true: any WMI query can be run remotely, including event notification queries.&lt;br /&gt;&lt;br /&gt;
To initiate a remote WMI connection (assuming all required services are correctly configured) you need to have administrative rights on the local computer. Also, you have to be using (or be able to specify the credentials of) an account with the rights to connect to the target computer and which has have the Remote Enable access permission on the WMI namespace you want to connect to. (For computers not in a domain, the account should have the same username and password on each computer). This access right is given by default to the Administrators group only, so effectively administrative rights are needed on the target computer. This makes the security model for remote access very simple - if you can initiate a connection, you get access to everything you would normally have access to, and as you're an admin that's everything anyway.&lt;br /&gt;&lt;br /&gt;
This sample class illustrates local and remote connections. It should work in VFP6 or later. Leave username, domain and password blank to connect with the current user context: from a security point of view, this is preferable as ConnectServer sends the password across the network as plain text unless your network uses Kerberos authentication.
&lt;h4&gt;WMI connection class&lt;/h4&gt;
&lt;pre&gt;
** Example usage: query a remote PC for the startup commands buried in the registry
oWMIConnection = createobject("WMIConnect")
oWMIConnection.cDomain = "domain"
oWMIConnection.cUsername = "remoteadmin"
* Computer can be specified by IP address or DNS name.
oWMIConnection.cComputer = "target" 
oWMIConnection.cPassword = "itsasecret"
oWMI = oWMIConnection.GetConnection()
if vartype(oWMI) = "O"
	oCommands = oWMI.ExecQuery("Select * from WIN32_StartupCommand")
	? transform(oCommands.Count) + " startup commands found"
endif

** WMI connection class

define class WMIConnect as relation

	** User/domain settings
	cDomain = ""
	cUserName = ""
	cPassword = ""

	** Target computer. 
	cComputer = "" 

	** WMI settings
	cNameSpace = "root/cimv2"

	* Without this flag, a connection attempt would wait indefinitely.
	* nFlags = 128 &amp;&amp; XP or better only..
	nFlags = 0

	* Find your Locale ID here: 
	* http://www.microsoft.com/globaldev/reference/win2k/setup/lcid.mspx
	cLocale = "MS_409" &amp;&amp; EN-US

	** Security settings.
	nImpersonationLevel = 3 &amp;&amp; wbemImpersonationLevelImpersonate
	nAuthenticationLevel = 0 &amp;&amp; wbemAuthenticationLevelDefault

	* If .T., load all privileges before connection.
	lLoadPrivileges = .f.

	procedure GetConnection

		local cUserName, cPassword, lRemote, oWMILocator, ;
			oPrivileges, oWMIServices, nPrivilegeID

		if empty(this.cComputer)
			* Use "." for a local connection
			this.cComputer = "."
		endif

		if this.cComputer = "."
			* Cannot use credentials to connect locally
			* even if they are the ones currently in use.
			cUserName = ""
			cPassword = ""
			lRemote = .f.
		else
			cUserName = iif(not empty(this.cDomain), this.cDomain + "\", "") + this.cUserName
			cPassword = this.cPassword
			lRemote = .t.
		endif

		oWMILocator = createobject("WbemScripting.SWbemLocator")

		if lRemote
			* Apply settings for DCOM:
			oWMILocator.Security_.ImpersonationLevel = this.nImpersonationLevel
			oWMILocator.Security_.AuthenticationLevel = this.nAuthenticationLevel
		else
			* Privileges only need to be applied for on local system
			* but must be requested *before* connection on Windows 9x/NT
			if this.lLoadPrivileges
				for nPrivilegeID = 1 to 27
					oWMILocator.Security_.Privileges.add(nPrivilegeID)
				next
			endif
		endif

		cAuthority = "" &amp;&amp; for kerberos, see PSDK

		* Connect as specified...
		oWMIServices = oWMILocator.ConnectServer ;
			(this.cComputer, this.cNameSpace, cUserName, cPassword, ;
			this.cLocale, cAuthority, this.nFlags)

		* ... and return the WMI service object.
		return oWMIServices

	endproc

	procedure error(nError, cMethod, nLine)

		* Sample error code
		* This needs to be tested across versions to make sure 
		* same errors are raised.
		local arrErr[1]
		aerror(arrErr)
		display memory like arrErr

		if nError = 1429 or nError = 1427
			nError = arrErr(7)
		else
			messagebox("Error " + transform(nError) + " has occurred")
			* Oops..
		endif

		do case		
			case nError = 4110
				? "Invalid namespace"
			case nError = 4099
				? "Valid credentials, but not allowed to connect"
			case nError = 5
				? "Invalid credentials"
			case nError = 1722
				? "Remote computer not found" 
		endcase
	endproc

enddefine

&lt;/pre&gt;

&lt;h4&gt;Impersonation and Authentication&lt;/h4&gt; 

The &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/wbemimpersonationlevelenum.asp"&gt;Impersonation Level&lt;/a&gt; specifies whether your security credentials are available to WMI. Of the four options, the documentation says of two that "calls to WMI may fail with this impersonation level" and the fourth "may constitute an unnecessary security risk." This leaves the value "impersonate", and this is the default in Windows 2000 or later - or, to be completely accurate, the value in the registry key 'HKLM\Software\Microsoft\wbem\Scripting\Default Impersonation Level' is the default. This is why impersonationLevel is set to impersonate in all WMI scripts designed for potential remote use.
&lt;br /&gt;&lt;br /&gt;
The &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/wbemauthenticationlevelenum.asp"&gt;Authentication Level&lt;/a&gt; determines how secure the connection is. This is constrained by the target operating system: Windows XP won't accept a connection less than 'packet', Windows 9x can't accept connections greater than 'connect'. Fortunately, using the default setting (wbemAuthenticationLevelDefault) allows DCOM to negotiate the appropriate security level.&lt;br /&gt;&lt;br /&gt;
There are a number of complications when connecting between different versions of Windows, and sometimes specific impersonation and authentication levels need to be set - see &lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/connecting_between_different_operating_systems.asp"&gt;this document&lt;/a&gt; for the details. Generally, going downstream - from newer to older - is easier than upstream; crossing two or more generations of Windows either way can be problematic and is sometimes impossible.&lt;br /&gt;
&lt;h4&gt;Optimising the performance of remote queries&lt;/h4&gt;
&lt;li&gt;Add the WBEM_FLAG_FORWARD_ONLY flag to get one-time forward-enumeration objects: the returned collection object does not populate it's Count property (or support the Clone_ method) and is released as soon as it has been enumerated.&lt;/li&gt;
&lt;li&gt;If practical, specify a specific field list rather than * in the SELECT statement.&lt;/li&gt;
&lt;li&gt;Don't use moniker syntax even if you are connecting with the current user account.&lt;/li&gt;
&lt;li&gt;Use as specific a WHERE clause as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;Setting WMI security with the Windows WMI Control&lt;/h4&gt;
The WMI access permissions are set via the WMI control, which is hidden in the Services and Applications section of Windows 2000/XP Computer Management [or via start/run/wmimgmt.msc]. To access it, right-click on "WMI Control" and choose Properties. Here you can check the default WMI namespace, activate some very verbose logging, and back up the WMI repository. On the security tab is a familiar Windows Access Control Entries (ACE) dialog which enables allowing or denying specific permissions to each namespace on your computer. Here you can disable remote access to WMI namespaces, if you're paranoid but don't want to stop the WMI service altogether.&lt;br /&gt;&lt;br /&gt;
Alternatively, in some circumstances you could here give an account permission to make a remote connection (and perform some actions) without giving it administrative rights as such, on the general principle it's best to initiate connections with as few rights as possible in order to accomplish the task at hand. (The account will also need DCOM remote connect rights, in Windows 2000/XP these are set with the other DCOM settings in the Component Services manager which can be started from the Administrative Tools menu: right click on the "My Computer" entry in the "Computers" tree and choose "Properties" to access them. Use dcomcnfg.exe for &lt;a href="http://support.microsoft.com/kb/176799/EN-US/"&gt;NT&lt;/a&gt; and &lt;a href="http://support.microsoft.com/kb/182248/EN-US/"&gt;Windows 9x&lt;/a&gt;.

&lt;h4&gt;Troubleshooting&lt;/h4&gt;
&lt;h5&gt;Links&lt;/h5&gt;
&lt;ul&gt;&lt;li&gt;
&lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/securing_a_remote_wmi_connection.asp"&gt;Securing a Remote WMI Connection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmisdk/wmi/connecting_through_windows_firewall.asp"&gt;Connecting through Windows Firewall&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://groups.google.com/groups?selm=3E822EB9.C5D31E11%40hydro.com"&gt;The ForceGuest problem prevents connections made to Windows XP Pro in a Workgroup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://msdn.microsoft.com/library/en-us/wmisdk/wmi/connecting_between_different_operating_systems.asp"&gt;Connecting between different operating systems&lt;/a&gt; (shouldn't that be "versions of windows"?)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://www.microsoft.com/technet/community/columns/scripts/rem0503.mspx"&gt;Accessing a Win98SE Computer Remotely&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;Notes / command snippets&lt;/h5&gt;
netsh firewall set service type=remoteadmin mode=enable scope=all profile=all

&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=910" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1007.aspx">WMI</category></item><item><title>COM+ scripting</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2005/08/24/882.aspx</link><pubDate>Wed, 24 Aug 2005 21:53:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:882</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/882.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=882</wfw:commentRss><description>This is extracted from a COM+ admin class I wrote for a work project: it illustrates how to view the properties of a COM+ application and it's associated components. It needs Windows 2000 or XP to work, and will report on the system helper applications that are installed by default with COM+ if you don't have a COM+ application as such.

&lt;pre&gt;
* Admin Catalog interface:
* oAdminCatalog as COMAdmin.COMAdminCatalog

* Collection interfaces (oApplications, oComponents, oApplicationProperties)
* oApplications as COMAdmin.COMAdminCatalogCollection

* Object interface - oApp, oComponent, oProperty
* oApp as COMAdmin.COMAdminCatalogObjectb

clear

* cServer = "complus_server"
* use empty string for local computer
cServer = ""

* Root COM+ admin object
oAdminCatalog = .null.

* COM+ Applications objects
oApplications = .null.
oApp = .null.

* Component objects:
oComponents = .null.
oComponent = .null.

* Property objects
oApplicationProperties = .null.
oComponentProperties = .null.

* Go!
oAdminCatalog = createobject("COMAdmin.COMAdminCatalog")
oAdminCatalog.connect(cServer)

oApplications = oAdminCatalog.GetCollection("Applications")
oApplications.Populate()

for each oApp in oApplications

	* To ask the application collection what properties it supports
	* we need to send a key - zero will do.
	oApplicationProperties = oApplications.GetCollection("PropertyInfo", 0)
	oApplicationProperties.Populate()

	* Send the app's CLSID to get the components collection:
	oComponents = oApplications.GetCollection("Components", oApp.key)

	* Ask the components collection what properties it supports
	oComponentProperties = oComponents.GetCollection("PropertyInfo", 0)
	oComponentProperties.Populate()

	* Only need to do this once.
	exit
next

for each oApp in oApplications

	? "Application: " + oApp.name
	? "--------------------------------------"
	GetApplicationProperties(oApp)
	?

	oComponents = oApplications.GetCollection("Components", oApp.key)
	oComponents.Populate()

	for each oComponent in oComponents
		GetComponentProperties(oComponent)
		* One component per listing
		exit
	next
	
	* One application per listing
	exit
next

return

procedure GetApplicationProperties (oApp)

	local oProperty, cValue

	for each oProperty in oApplicationProperties

		? oProperty.name + ": "
		* Properties - e.g. 'Password' - can be write only.
		if oApp.IsPropertyWriteOnly(oProperty.name) = .f.
			cValue = transform(nvl(oApp.value(oProperty.name), ""))
		else
			cValue = "[Write only]"
		endif
		?? cValue
	next

endproc

procedure GetComponentProperties(oComponent)

	local oProperty, cValue

	? "Component: " + oComponent.name
	? "--------------------------------------"

	for each oProperty in oComponentProperties

		? oProperty.name + ": "

		* Properties - e.g. 'Password' - can be write only.
		if oComponent.IsPropertyWriteOnly(oProperty.name) = .f.
			cValue = transform(nvl(oComponent.value(oProperty.name), ""))
		else
			cValue = "[Write only]"
		endif
		?? cValue
	next

endproc



&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=882" width="1" height="1"&gt;</description></item><item><title>Accessing user information from Foxpro with ADSI</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2005/08/05/857.aspx</link><pubDate>Fri, 05 Aug 2005 18:49:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:857</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/857.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=857</wfw:commentRss><description>There are a number of ways by which an application can gather information about the current user, whether this is a local computer account, a domain account, or an Active Directory account. Workstations using Windows NT or 9x need to have the Active Directory client installed - download from &lt;a href="http://support.microsoft.com/kb/288358"&gt;here&lt;/a&gt;.&lt;br&gt;&lt;br&gt;There are two protocols available for accessing directory information:&lt;br&gt;&lt;ul&gt;&lt;li&gt;WinNT:// is required to access users on NT domains or for Active Directory queries from a PC which isn't a member workstation in the Active Directory.&lt;/li&gt;&lt;li&gt;LDAP:// gives full access to the Active Directory.&lt;/li&gt;&lt;/ul&gt;[Test LDAP:// with local accounts on winxp, 2k etc]&lt;br&gt;&lt;br&gt;This code illustrates how to enumerate the Property collections of ADSI objects to show what is available and how to access the values (each property type has an associated method to return the value).  It can use the WinNT or LDAP. Useful references are &lt;a href="http://msdn.microsoft.com/library/en-us/adsi/adsi/iadsuser.asp"&gt;the Active Directory user class&lt;/a&gt;, the &lt;a href="http://msdn.microsoft.com/library/en-us/adsi/adsi/unsupported_iadsuser_properties.asp"&gt;list of properties not supported by WinNT:&lt;/a&gt; and the &lt;a href="http://msdn.microsoft.com/library/en-us/adsi/adsi/winnt_custom_user_properties.asp"&gt;extra properties added&lt;/a&gt;. - the latter list is a lot shorter than the former. Schema extensions such as Microsoft Exchange can be accessed via LDAP which can drastically increase the amount of information available.. Each schema has a helper object - &lt;a href="http://msdn.microsoft.com/library/en-us/adsi/adsi/iadswinntsysteminfo.asp"&gt;WinNTSystemInfo&lt;/a&gt; and &lt;a href="http://msdn.microsoft.com/library/en-us/adsi/adsi/iadsadsysteminfo.asp"&gt;ADSystemInfo&lt;/a&gt; - to provide the information required to access user information.&lt;br&gt;&lt;br&gt;The code in this article illustrates how to access the local user information (and, if connected to a domain, workstation information) using WinNT and LDAP. It shows how to extract the individual properties from the UserAccountControl property (UserFlags in WinNT), and how to access property values via the Properties collection. It also illustrates how to calculate the password expiry date under each protocol.&lt;br&gt;&lt;pre&gt;clear&lt;br&gt;&lt;br&gt;local oWinNTSystemInfo, cDomain, cUserName, cComputer, ;&lt;br&gt;	oDomain, oUser, oComputer, oADSystemInfo, lLDAP, ;&lt;br&gt;	nUserFlags, tExpiryDateTime, oGroup&lt;br&gt;&lt;br&gt;* Use WinNT:// by default - this should work on any system with the AD client.&lt;br&gt;oWinNTSystemInfo = createobject("WinNTSystemInfo")&lt;br&gt;&lt;br&gt;cDomain = oWinNTSystemInfo.DomainName&lt;br&gt;cUserName = oWinNTSystemInfo.UserName&lt;br&gt;cComputer = oWinNTSystemInfo.ComputerName&lt;br&gt;&lt;br&gt;* If WinNTSystemInfo doesn't work as expected in all environments&lt;br&gt;*	 cDomain = "." &amp;amp;&amp;amp; local machine&lt;br&gt;*	 cUserName = alltrim(substr(sys(0), at("#", sys(0)) + 1))&lt;br&gt;*	 cComputer = alltrim(left(sys(0), at("#", sys(0)) - 1))&lt;br&gt;&lt;br&gt;* NOTE: these monikers are case-sensitive:&lt;br&gt;* Winnt: and WINNT: and ldap: and Ldap: won't work&lt;br&gt;&lt;br&gt;oDomain = getobject("WinNT://" + cDomain)&lt;br&gt;oUser = getobject("WinNT://" + cDomain + "/" + cUserName + ",user")&lt;br&gt;&lt;br&gt;try &amp;amp;&amp;amp; error if not connected to domain&lt;br&gt;	oComputer = getobject("WinNT://" + cDomain + "/" + cComputer + ",computer")&lt;br&gt;catch&lt;br&gt;endtry&lt;br&gt;&lt;br&gt;* Try to use LDAP://&lt;br&gt;oADSystemInfo = createobject("ADSystemInfo")&lt;br&gt;&lt;br&gt;try&lt;br&gt;	* PCs not connected to a domain will error as soon as any property is accessed&lt;br&gt;	cDomain = oADSystemInfo.DomainShortName&lt;br&gt;	cUserName = oADSystemInfo.UserName&lt;br&gt;	cComputer = oADSystemInfo.ComputerName&lt;br&gt;&lt;br&gt;	oDomain = getobject("LDAP://" + cDomain)&lt;br&gt;	oUser = getobject("LDAP://" + cUserName)&lt;br&gt;	oComputer = getobject("LDAP://" + cComputer)&lt;br&gt;&lt;br&gt;	lLDAP = .t.&lt;br&gt;catch&lt;br&gt;	lLDAP = .f.&lt;br&gt;endtry&lt;br&gt;&lt;br&gt;* The rest of the code works whether using LDAP or WinNT.&lt;br&gt;&lt;br&gt;* The AccountDisabled property doesn't appear in the property list&lt;br&gt;* but it's available without calling GetInfo which would save&lt;br&gt;* resources when enumerating a list of users.&lt;br&gt;&lt;br&gt;* Test local guest user is disabled with&lt;br&gt;* oUser = getobject("WinNT://./Guest,user")&lt;br&gt;&lt;br&gt;if oUser.AccountDisabled&lt;br&gt;	? "Account is disabled"&lt;br&gt;	return&lt;br&gt;endif&lt;br&gt;&lt;br&gt;? "User flags"&lt;br&gt;? "--------------"&lt;br&gt;if lLDAP&lt;br&gt;	nUserFlags = oUser.UserAccountControl&lt;br&gt;else&lt;br&gt;	nUserFlags = oUser.UserFlags&lt;br&gt;endif&lt;br&gt;GetUserFlags(nUserFlags)&lt;br&gt;&lt;br&gt;?&lt;br&gt;? "User properties"&lt;br&gt;? "---------------------"&lt;br&gt;&lt;br&gt;* Populate the properties (the data may be there but the propertycount&lt;br&gt;* isn't until this has been called)&lt;br&gt;oUser.GetInfo()&lt;br&gt;* Pass True as second parameter to see all the properties and values&lt;br&gt;ShowProperties(oUser, .f.)&lt;br&gt;&lt;br&gt;* Groups, workstation&lt;br&gt;?&lt;br&gt;? "Group memberships"&lt;br&gt;? "---------------------"&lt;br&gt;&lt;br&gt;for each oGroup in oUser.Groups&lt;br&gt;	? oGroup.name&lt;br&gt;next&lt;br&gt;&lt;br&gt;?&lt;br&gt;? "First group's properties"&lt;br&gt;? "---------------------"&lt;br&gt;for each oGroup in oUser.Groups&lt;br&gt;	oGroup.GetInfo()&lt;br&gt;	ShowProperties(oGroup, .f.)&lt;br&gt;	exit&lt;br&gt;next&lt;br&gt;&lt;br&gt;&lt;br&gt;?&lt;br&gt;? "Workstation properties"&lt;br&gt;? "---------------------"&lt;br&gt;&lt;br&gt;if vartype(oComputer) = "O"&lt;br&gt;	oComputer.GetInfo()&lt;br&gt;	ShowProperties(oComputer, .f.)&lt;br&gt;else&lt;br&gt;	? "Not available"&lt;br&gt;endif&lt;br&gt;&lt;br&gt;?&lt;br&gt;? "Password expiry"&lt;br&gt;? "---------------------"&lt;br&gt;&lt;br&gt;if lLDAP&lt;br&gt;	tExpiryDateTime = GetPasswordExpiryLDAP(oDomain, oUser)&lt;br&gt;else&lt;br&gt;	tExpiryDateTime = GetPasswordExpiryWinNT(oDomain, oUser)&lt;br&gt;endif&lt;br&gt;&lt;br&gt;if not empty(tExpiryDateTime)&lt;br&gt;	? "Password expires " + ttoc(tExpiryDateTime)&lt;br&gt;endif&lt;br&gt;&lt;br&gt;procedure GetPasswordExpiryLDAP&lt;br&gt;&lt;br&gt;	lparameters oDomain, oUser&lt;br&gt;	* Firstly determine the password policy:&lt;br&gt;	local oMaxPwdAge, dExpiryDate, nNumDays&lt;br&gt;&lt;br&gt;	oMaxPwdAge = oDomain.get("maxPwdAge")&lt;br&gt;	if oMaxPwdAge.LowPart = 0&lt;br&gt;		* Domain policy doesn't expire passwords&lt;br&gt;		dExpiryDate = {--}&lt;br&gt;	else&lt;br&gt;		* Number of 100 nanosecond intervals since 1.1.1601!&lt;br&gt;		nNumDays =  ((oMaxPwdAge.HighPart * 2 ^ 32) + ;&lt;br&gt;			oMaxPwdAge.LowPart) / -864000000000&lt;br&gt;		* Convert the policy to seconds to match PasswordLastChanged&lt;br&gt;		* and calculate expiry date and time.&lt;br&gt;		dExpiryDate = oUser.PasswordLastChanged + (nNumDays * 24 * 60 * 60)&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;&lt;br&gt;	return dExpiryDate&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;br&gt;procedure GetPasswordExpiryWinNT&lt;br&gt;&lt;br&gt;	lparameters oDomain, oUser&lt;br&gt;&lt;br&gt;	* Firstly determine the password policy:&lt;br&gt;	local nMaxPasswordAge, nActualPasswordAge, tExpiry&lt;br&gt;	try&lt;br&gt;		* Errors for local accounts which don't have MaxPasswordAge set&lt;br&gt;		nMaxPasswordAge = oDomain.MaxPasswordAge &amp;amp;&amp;amp; in seconds&lt;br&gt;		nActualPasswordAge = oUser.PasswordAge &amp;amp;&amp;amp; in seconds&lt;br&gt;		* Calculate expiry date and time.&lt;br&gt;		tExpiry = datetime() + nMaxPasswordAge - nActualPasswordAge&lt;br&gt;	catch&lt;br&gt;		tExpiry = {--::}&lt;br&gt;	endtry&lt;br&gt;&lt;br&gt;	return  tExpiry&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;br&gt;procedure ShowProperties&lt;br&gt;&lt;br&gt;	lparameters oObject, lVerbose&lt;br&gt;	local oProperty, nCount, oValue&lt;br&gt;&lt;br&gt;	if lVerbose&lt;br&gt;		for nCount = 0 to oObject.PropertyCount - 1&lt;br&gt;&lt;br&gt;			* Access the properties collection by item&lt;br&gt;			oProperty = oObject.item[nCount]&lt;br&gt;&lt;br&gt;			if not isnull(oProperty.values)&lt;br&gt;				for each oValue in oProperty.values&lt;br&gt;					? oProperty.name + " : " + transform(GetPropertyValue(oValue))&lt;br&gt;				next&lt;br&gt;			else&lt;br&gt;				? oProperty.name + " is null"&lt;br&gt;			endif&lt;br&gt;		next&lt;br&gt;	else&lt;br&gt;		? "Object has " + ltrim(str(oObject.PropertyCount)) + " properties"&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;br&gt;procedure GetPropertyValue&lt;br&gt;&lt;br&gt;	lparameters oValue&lt;br&gt;&lt;br&gt;	local nType&lt;br&gt;	nType = oValue.ADSType&lt;br&gt;&lt;br&gt;	* Each type has it's own method to return the value..?&lt;br&gt;	* See below for the full list.&lt;br&gt;&lt;br&gt;	do case&lt;br&gt;		case nType = 1&lt;br&gt;			return oValue.DNString()&lt;br&gt;&lt;br&gt;		case nType = 2&lt;br&gt;			return oValue.CaseExactString()&lt;br&gt;&lt;br&gt;		case nType = 3&lt;br&gt;			return oValue.CaseIgnoreString()&lt;br&gt;&lt;br&gt;		case nType = 4&lt;br&gt;			return oValue.PrintableString()&lt;br&gt;&lt;br&gt;		case nType = 5&lt;br&gt;			return oValue.NumericString()&lt;br&gt;&lt;br&gt;		case nType = 6&lt;br&gt;			return oValue.Boolean()&lt;br&gt;&lt;br&gt;		case nType = 7&lt;br&gt;			* Reserved word&lt;br&gt;			return oValue.integer()&lt;br&gt;&lt;br&gt;		case nType = 8&lt;br&gt;			return oValue.OctetString()&lt;br&gt;&lt;br&gt;		case nType = 9&lt;br&gt;			return oValue.UTCTime()&lt;br&gt;&lt;br&gt;		case nType = 10&lt;br&gt;			return oValue.LargeInteger()&lt;br&gt;&lt;br&gt;		case nType = 25&lt;br&gt;			return oValue.SecurityDescriptor()&lt;br&gt;&lt;br&gt;		otherwise&lt;br&gt;			* Other types can't be obtained through this interface.&lt;br&gt;			return .null.&lt;br&gt;&lt;br&gt;	endcase&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;br&gt;procedure GetUserFlags&lt;br&gt;&lt;br&gt;	lparameters nFlags&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 0) &amp;amp;&amp;amp; ADS_UF_SCRIPT, 1&lt;br&gt;		? "The logon script is executed."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 1) &amp;amp;&amp;amp; ADS_UF_ACCOUNTDISABLE, 2&lt;br&gt;		? "This user account is disabled."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 3) &amp;amp;&amp;amp; ADS_UF_HOMEDIR_REQUIRED, 8&lt;br&gt;		? "The home directory is required."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 4) &amp;amp;&amp;amp; ADS_UF_LOCKOUT, 16&lt;br&gt;		? "This account is currently locked out."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 5) &amp;amp;&amp;amp; ADS_UF_PASSWD_NOTREQD, 32&lt;br&gt;		? "No password is required for this account."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 6) &amp;amp;&amp;amp; ADS_UF_PASSWD_CANT_CHANGE, 64&lt;br&gt;		? "This user cannot change their password."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 7) &amp;amp;&amp;amp; ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED, 128&lt;br&gt;		? "This user can send an encrypted password."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 8) &amp;amp;&amp;amp; ADS_UF_TEMP_DUPLICATE_ACCOUNT, 256&lt;br&gt;		? "This is an account for users whose primary account is in another domain."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 9) &amp;amp;&amp;amp; ADS_UF_NORMAL_ACCOUNT, 512&lt;br&gt;		? "This is a default account type that represents a normal user."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 16) &amp;amp;&amp;amp; ADS_UF_DONT_EXPIRE_PASSWD, 65536&lt;br&gt;		? "The password will not expire on this account."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	if bittest(nFlags, 23) &amp;amp;&amp;amp; ADS_UF_PASSWORD_EXPIRED, 8388608&lt;br&gt;		? "This user's password has expired."&lt;br&gt;	endif&lt;br&gt;&lt;br&gt;	* Full list of flags at&lt;br&gt;	* http://msdn.microsoft.com/library/en-us/adsi/adsi/ads_user_flag_enum.asp&lt;br&gt;&lt;br&gt;endproc&lt;br&gt;&lt;br&gt;&lt;br&gt;* Property type constants&lt;br&gt;&lt;br&gt;#define ADSTYPE_INVALID			0	&amp;amp;&amp;amp;	The data type is invalid.&lt;br&gt;#define ADSTYPE_DN_STRING		1	&amp;amp;&amp;amp;	the string is a Distinguished Name (path) of a directory service object.&lt;br&gt;#define ADSTYPE_CASE_EXACT_STRING	2	&amp;amp;&amp;amp;	the string is the case-sensitive type.&lt;br&gt;#define ADSTYPE_CASE_IGNORE_STRING	3	&amp;amp;&amp;amp;	the string is the case-insensitive type.&lt;br&gt;#define ADSTYPE_PRINTABLE_STRING	4	&amp;amp;&amp;amp;	The string is displayable on screen or in print.&lt;br&gt;#define ADSTYPE_NUMERIC_STRING		5	&amp;amp;&amp;amp;	the string is a numeral to be interpreted as text.&lt;br&gt;#define ADSTYPE_BOOLEAN			6	&amp;amp;&amp;amp;	the data is a Boolean value.&lt;br&gt;#define ADSTYPE_INTEGER			7	&amp;amp;&amp;amp;	the data is an integer value.&lt;br&gt;#define ADSTYPE_OCTET_STRING		8	&amp;amp;&amp;amp;	the string is a byte array.&lt;br&gt;#define ADSTYPE_UTC_TIME		9	&amp;amp;&amp;amp;	the data is the universal time as expressed in Universal Time Coordinate (UTC).&lt;br&gt;#define ADSTYPE_LARGE_INTEGER		10	&amp;amp;&amp;amp;	the data is a long integer value.&lt;br&gt;#define ADSTYPE_PROV_SPECIFIC		11	&amp;amp;&amp;amp;	the string is a provider-specific string.&lt;br&gt;#define ADSTYPE_OBJECT_CLASS		12	&amp;amp;&amp;amp;	Not used.&lt;br&gt;#define ADSTYPE_CASEIGNORE_LIST		13	&amp;amp;&amp;amp; 	the data is a list of case insensitive strings.&lt;br&gt;#define ADSTYPE_OCTET_LIST		14	&amp;amp;&amp;amp;	the data is a list of octet strings.&lt;br&gt;#define ADSTYPE_PATH			15	&amp;amp;&amp;amp;	the string is a directory path.&lt;br&gt;#define ADSTYPE_POSTALADDRESS		16	&amp;amp;&amp;amp; 	the string is the postal address type.&lt;br&gt;#define ADSTYPE_TIMESTAMP		17	&amp;amp;&amp;amp;	the data is a time stamp in seconds.&lt;br&gt;#define ADSTYPE_BACKLINK		18	&amp;amp;&amp;amp;	the string is a back link.&lt;br&gt;#define ADSTYPE_TYPEDNAME		19	&amp;amp;&amp;amp;	the string is a typed name.&lt;br&gt;#define ADSTYPE_HOLD			20	&amp;amp;&amp;amp;	the data is the Hold data structure.&lt;br&gt;#define ADSTYPE_NETADDRESS		21	&amp;amp;&amp;amp;	the string is a net address.&lt;br&gt;#define ADSTYPE_REPLICAPOINTER		22	&amp;amp;&amp;amp; 	the data is a replica pointer.&lt;br&gt;#define ADSTYPE_FAXNUMBER		23	&amp;amp;&amp;amp;	the string is a fax number.&lt;br&gt;#define ADSTYPE_EMAIL			24	&amp;amp;&amp;amp;	the data is an e-mail message.&lt;br&gt;#define ADSTYPE_NT_SECURITY_DESCRIPTOR	25	&amp;amp;&amp;amp;	the data is Windows NT/Windows 2000 security descriptor as represented by a byte array.&lt;br&gt;#define ADSTYPE_UNKNOWN			26	&amp;amp;&amp;amp;	the data is an undefined type.&lt;br&gt;#define ADSTYPE_DN_WITH_BINARY		27	&amp;amp;&amp;amp;	The data is used for mapping a distinguished name to a non varying GUID.&lt;br&gt;#define ADSTYPE_DN_WITH_STRING		28	&amp;amp;&amp;amp;	The data is used for mapping a distinguished name to a non-varying string value.&lt;br&gt;&lt;br&gt;* ADS Name types&lt;br&gt;#define ADS_NAME_TYPE_1779	    		1&lt;br&gt;#define ADS_NAME_TYPE_CANONICAL			2&lt;br&gt;#define ADS_NAME_TYPE_NT4	     		3&lt;br&gt;#define ADS_NAME_TYPE_DISPLAY	     		4&lt;br&gt;#define ADS_NAME_TYPE_DOMAIN_SIMPLE		5&lt;br&gt;#define ADS_NAME_TYPE_ENTERPRISE_SIMPLE		6&lt;br&gt;#define ADS_NAME_TYPE_GUID	     		7&lt;br&gt;#define ADS_NAME_TYPE_UNKNOWN	     		8&lt;br&gt;#define ADS_NAME_TYPE_USER_PRINCIPAL_NAME	9&lt;br&gt;#define ADS_NAME_TYPE_CANONICAL_EX	     	10&lt;br&gt;#define ADS_NAME_TYPE_SERVICE_PRINCIPAL_NAME	11&lt;br&gt;#define ADS_NAME_TYPE_SID_OR_SID_HISTORY_NAME	12&lt;br&gt;&lt;br&gt;  &lt;br&gt;&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=857" width="1" height="1"&gt;</description><category domain="http://weblogs.foxite.com/stuartdunkeld/archive/category/1009.aspx">ADSI</category></item><item><title>A WMI wrapper class</title><link>http://weblogs.foxite.com/stuartdunkeld/archive/2005/07/15/778.aspx</link><pubDate>Fri, 15 Jul 2005 21:00:00 GMT</pubDate><guid isPermaLink="false">8827bd1c-7596-4a8f-b0de-f59ce9ede522:778</guid><dc:creator>stuartd</dc:creator><slash:comments>0</slash:comments><comments>http://weblogs.foxite.com/stuartdunkeld/comments/778.aspx</comments><wfw:commentRss>http://weblogs.foxite.com/stuartdunkeld/commentrss.aspx?PostID=778</wfw:commentRss><description>This VFP class will return a populated VFP object reflecting an instance of the specified WMI class: if you're using VFP9 you benefit from the correct capitalisation when using intellisense, as it creates _memberdata for all the class properties. The code could easily return a specific - i.e. keyed - instance.

Usage:&lt;br&gt;&lt;br&gt;

&lt;img src="http://img300.imageshack.us/img300/4339/wmiwrapper1gf.png"&gt; 

&lt;br&gt;&lt;br&gt;

&lt;pre&gt;
define class WMIWrapper as relation

	cClass = "WIN32_OperatingSystem"
	oClass = .null.
	cMemberData = ""

	procedure generate (cClass as string) as object

		local cName as string, oProperty as object, oItem as object, vValue

		if not empty(cClass)
			this.cClass = cClass
		endif

		this.GetClass()
		

		this.cMemberData = "&amp;lt;VFPData&amp;gt;"

		oItem = createobject("empty")

		for each oProperty in this.oClass.Properties_

			cName = oProperty.name

			if oProperty.IsArray
				vValue = "[Array]"
			else
				vValue = oProperty.value
			endif

			addproperty(oItem, cName, vValue)

			this.cMemberData = this.cMemberData + ;
				[&amp;lt;] + [memberdata ] +  ;
				[name="] + lower(cName) + ["] + ;
				[ type="property" display="] + cName + ["/] + [&amp;gt;]
				
		next

		this.cMemberData = this.cMemberData + "&amp;lt;/VFPData&amp;gt;"

		if version(5) =&amp;gt; 900
			addproperty(oItem, "_memberdata", this.cMemberData)
		endif

		return oItem

	endproc

	procedure GetClass

		local oWMI, oItems, oItem

		oWMI = getobject("winmgmts:")
		oItems = oWMI.ExecQuery("select * from " + this.cClass)

		for each oItem in oItems
			this.oClass = oItem
			exit
		next

	endproc

enddefine

&lt;/pre&gt;&lt;img src="http://weblogs.foxite.com/aggbug.aspx?PostID=778" width="1" height="1"&gt;</description></item></channel></rss>