Category: ASP.NET

Leveraging WMI in an Azure Web Role

Posted by Wade on November 4, 2009 | No comments

If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!

A few weeks ago I threw out a teaser on Twitter:

Want to read the event logs in Windows Azure?

This got the attention of a few folks, and I promised I’d follow-up on this with some details on how I got this to work.  Of course, I completely forgot to do so, and was only recently reminded (thanks, Roger Jennings).

You can still take a look at that application here (if nothing else, I find it interesting to look at the specifications of the CTP machines for Windows Azure): http://wmi.cloudapp.net/

In actuality, the solution is really quite straightforward – I used WMI.

What’s WMI?  Well, it stands for Windows Management Instrumentation.  Here’s quick blurb from MSDN:

Windows Management Instrumentation (WMI) is the infrastructure for management data and operations on Windows-based operating systems. You can write WMI scripts or applications to automate administrative tasks on remote computers but WMI also supplies management data to other parts of the operating system and products, for example System Center Operations Manager, formerly Microsoft Operations Manager (MOM), or Windows Remote Management.

For more information on how to use WMI in .NET, take a look at the WMI .NET Overview.  Lots of great information.

Assuming you’ve read the overview, it should be clear that this is really pretty easy.  At the heart of this are the following two lines of code …

ManagementClass mgmt = new ManagementClass(managementClass);
ManagementObjectCollection objCol = mgmt.GetInstances();

… where managementClass represent one of the Win32 classes (i.e. Win32_NTLogEventLog).

Now, to make this work, you need to iterate through the collection of management objects.  Something like the following works:

foreach (var obj in objCol)
{
    foreach (var prop in obj.Properties)
    {
        ...
    }
}

Now, I did two additional things to make this easy.

  1. I created a dropdown list with all the Win32 classes so that you can easily choose which class to review.
  2. I defined a querystring value that, if it exists, loads a specific Win32 class.

There are a lot of Win32 classes, so here’s the array I used – feel free to leverage it yourself:

public static string[] Win32Classes =
{
    "Win32_1394Controller",
    "Win32_1394ControllerDevice",
    "Win32_Account",
    "Win32_AccountSID",
    "Win32_ACE",
    "Win32_ActionCheck",
    "Win32_ActiveRoute",
    "Win32_AllocatedResource",
    "Win32_ApplicationCommandLine",
    "Win32_ApplicationService",
    "Win32_AssociatedBattery",
    "Win32_AssociatedProcessorMemory",
    "Win32_AutochkSetting",
    "Win32_BaseBoard",
    "Win32_BaseService",
    "Win32_Battery",
    "Win32_Binary",
    "Win32_BindImageAction",
    "Win32_BIOS",
    "Win32_BootConfiguration",
    "Win32_Bus",
    "Win32_CacheMemory",
    "Win32_CDROMDrive",
    "Win32_CheckCheck",
    "Win32_CIMLogicalDeviceCIMDataFile",
    "Win32_ClassicCOMApplicationClasses",
    "Win32_ClassicCOMClass",
    "Win32_ClassicCOMClassSetting",
    "Win32_ClassicCOMClassSettings",
    "Win32_ClassInfoAction",
    "Win32_ClientApplicationSetting",
    "Win32_CodecFile",
    "Win32_CollectionStatistics",
    "Win32_COMApplication",
    "Win32_COMApplicationClasses",
    "Win32_COMApplicationSettings",
    "Win32_COMClass",
    "Win32_ComClassAutoEmulator",
    "Win32_ComClassEmulator",
    "Win32_CommandLineAccess",
    "Win32_ComponentCategory",
    "Win32_ComputerShutdownEvent",
    "Win32_ComputerSystem",
    "Win32_ComputerSystemEvent",
    "Win32_ComputerSystemProcessor",
    "Win32_ComputerSystemProduct",
    "Win32_ComputerSystemWindowsProductActivationSetting",
    "Win32_COMSetting",
    "Win32_Condition",
    "Win32_ConnectionShare",
    "Win32_ControllerHasHub",
    "Win32_CreateFolderAction",
    "Win32_CurrentProbe",
    "Win32_CurrentTime",
    "Win32_DCOMApplication",
    "Win32_DCOMApplicationAccessAllowedSetting",
    "Win32_DCOMApplicationLaunchAllowedSetting",
    "Win32_DCOMApplicationSetting",
    "Win32_DefragAnalysis",
    "Win32_DependentService",
    "Win32_Desktop",
    "Win32_DesktopMonitor",
    "Win32_DeviceBus",
    "Win32_DeviceChangeEvent",
    "Win32_DeviceMemoryAddress",
    "Win32_DeviceSettings",
    "Win32_DFSNode",
    "Win32_DFSNodeTarget",
    "Win32_DFSTarget",
    "Win32_Directory",
    "Win32_DirectorySpecification",
    "Win32_DiskDrive",
    "Win32_DiskDrivePhysicalMedia",
    "Win32_DiskDriveToDiskPartition",
    "Win32_DiskPartition",
    "Win32_DiskQuota",
    "Win32_DisplayConfiguration",
    "Win32_DisplayControllerConfiguration",
    "Win32_DMAChannel",
    "Win32_DriverForDevice",
    "Win32_DriverVXD",
    "Win32_DuplicateFileAction",
    "Win32_Environment",
    "Win32_EnvironmentSpecification",
    "Win32_ExtensionInfoAction",
    "Win32_Fan",
    "Win32_FileSpecification",
    "Win32_FloppyController",
    "Win32_FloppyDrive",
    "Win32_FontInfoAction",
    "Win32_Group",
    "Win32_GroupInDomain",
    "Win32_GroupUser",
    "Win32_HeatPipe",
    "Win32_IDEController",
    "Win32_IDEControllerDevice",
    "Win32_ImplementedCategory",
    "Win32_InfraredDevice",
    "Win32_IniFileSpecification",
    "Win32_InstalledSoftwareElement",
    "Win32_IP4PersistedRouteTable",
    "Win32_IP4RouteTable",
    "Win32_IP4RouteTableEvent",
    "Win32_IRQResource",
    "Win32_JobObjectStatus",
    "Win32_Keyboard",
    "Win32_LaunchCondition",
    "Win32_LoadOrderGroup",
    "Win32_LoadOrderGroupServiceDependencies",
    "Win32_LoadOrderGroupServiceMembers",
    "Win32_LocalTime",
    "Win32_LoggedOnUser",
    "Win32_LogicalDisk",
    "Win32_LogicalDiskRootDirectory",
    "Win32_LogicalDiskToPartition",
    "Win32_LogicalFileAccess",
    "Win32_LogicalFileAuditing",
    "Win32_LogicalFileGroup",
    "Win32_LogicalFileOwner",
    "Win32_LogicalFileSecuritySetting",
    "Win32_LogicalMemoryConfiguration",
    "Win32_LogicalProgramGroup",
    "Win32_LogicalProgramGroupDirectory",
    "Win32_LogicalProgramGroupItem",
    "Win32_LogicalProgramGroupItemDataFile",
    "Win32_LogicalShareAccess",
    "Win32_LogicalShareAuditing",
    "Win32_LogicalShareSecuritySetting",
    "Win32_LogonSession",
    "Win32_LogonSessionMappedDisk",
    "Win32_LUID",
    "Win32_LUIDandAttributes",
    "Win32_ManagedSystemElementResource",
    "Win32_MappedLogicalDisk",
    "Win32_MemoryArray",
    "Win32_MemoryArrayLocation",
    "Win32_MemoryDevice",
    "Win32_MemoryDeviceArray",
    "Win32_MemoryDeviceLocation",
    "Win32_MethodParameterClass",
    "Win32_MIMEInfoAction",
    "Win32_ModuleLoadTrace",
    "Win32_ModuleTrace",
    "Win32_MotherboardDevice",
    "Win32_MountPoint",
    "Win32_MoveFileAction",
    "Win32_MSIResource",
    "Win32_NamedJobObject",
    "Win32_NamedJobObjectActgInfo",
    "Win32_NamedJobObjectLimit",
    "Win32_NamedJobObjectLimitSetting",
    "Win32_NamedJobObjectProcess",
    "Win32_NamedJobObjectSecLimit",
    "Win32_NamedJobObjectSecLimitSetting",
    "Win32_NamedJobObjectStatistics",
    "Win32_NetworkAdapter",
    "Win32_NetworkAdapterConfiguration",
    "Win32_NetworkAdapterSetting",
    "Win32_NetworkClient",
    "Win32_NetworkConnection",
    "Win32_NetworkLoginProfile",
    "Win32_NetworkProtocol",
    "Win32_NTDomain",
    "Win32_NTEventlogFile",
    "Win32_NTLogEvent",
    "Win32_NTLogEventComputer",
    "Win32_NTLogEventLog",
    "Win32_NTLogEventUser",
    "Win32_ODBCAttribute",
    "Win32_ODBCDataSourceAttribute",
    "Win32_ODBCDataSourceSpecification",
    "Win32_ODBCDriverAttribute",
    "Win32_ODBCDriverSoftwareElement",
    "Win32_ODBCDriverSpecification",
    "Win32_ODBCSourceAttribute",
    "Win32_ODBCTranslatorSpecification",
    "Win32_OnBoardDevice",
    "Win32_OperatingSystem",
    "Win32_OperatingSystemAutochkSetting",
    "Win32_OperatingSystemQFE",
    "Win32_OptionalFeature",
    "Win32_OSRecoveryConfiguration",
    "Win32_PageFile",
    "Win32_PageFileElementSetting",
    "Win32_PageFileSetting",
    "Win32_PageFileUsage",
    "Win32_ParallelPort",
    "Win32_Patch",
    "Win32_PatchFile",
    "Win32_PatchPackage",
    "Win32_PCMCIAController",
    "Win32_Perf",
    "Win32_PerfFormattedData",
    "Win32_PerfFormattedData_ASP_ActiveServerPages",
    "Win32_PerfFormattedData_ContentFilter_IndexingServiceFilter",
    "Win32_PerfFormattedData_ContentIndex_IndexingService",
    "Win32_PerfFormattedData_InetInfo_InternetInformationServicesGlobal",
    "Win32_PerfFormattedData_ISAPISearch_HttpIndexingService",
    "Win32_PerfFormattedData_MSDTC_DistributedTransactionCoordinator",
    "Win32_PerfFormattedData_NTFSDRV_SMTPNTFSStoreDriver",
    "Win32_PerfFormattedData_PerfDisk_LogicalDisk",
    "Win32_PerfFormattedData_PerfDisk_PhysicalDisk",
    "Win32_PerfFormattedData_PerfNet_Browser",
    "Win32_PerfFormattedData_PerfNet_Redirector",
    "Win32_PerfFormattedData_PerfNet_Server",
    "Win32_PerfFormattedData_PerfNet_ServerWorkQueues",
    "Win32_PerfFormattedData_PerfOS_Cache",
    "Win32_PerfFormattedData_PerfOS_Memory",
    "Win32_PerfFormattedData_PerfOS_Objects",
    "Win32_PerfFormattedData_PerfOS_PagingFile",
    "Win32_PerfFormattedData_PerfOS_Processor",
    "Win32_PerfFormattedData_PerfOS_System",
    "Win32_PerfFormattedData_PerfProc_FullImage_Costly",
    "Win32_PerfFormattedData_PerfProc_Image_Costly",
    "Win32_PerfFormattedData_PerfProc_JobObject",
    "Win32_PerfFormattedData_PerfProc_JobObjectDetails",
    "Win32_PerfFormattedData_PerfProc_Process",
    "Win32_PerfFormattedData_PerfProc_ProcessAddressSpace_Costly",
    "Win32_PerfFormattedData_PerfProc_Thread",
    "Win32_PerfFormattedData_PerfProc_ThreadDetails_Costly",
    "Win32_PerfFormattedData_PSched_PSchedFlow",
    "Win32_PerfFormattedData_PSched_PSchedPipe",
    "Win32_PerfFormattedData_RemoteAccess_RASPort",
    "Win32_PerfFormattedData_RemoteAccess_RASTotal",
    "Win32_PerfFormattedData_RSVP_ACSRSVPInterfaces",
    "Win32_PerfFormattedData_RSVP_ACSRSVPService",
    "Win32_PerfFormattedData_SMTPSVC_SMTPServer",
    "Win32_PerfFormattedData_Spooler_PrintQueue",
    "Win32_PerfFormattedData_TapiSrv_Telephony",
    "Win32_PerfFormattedData_Tcpip_ICMP",
    "Win32_PerfFormattedData_Tcpip_IP",
    "Win32_PerfFormattedData_Tcpip_NBTConnection",
    "Win32_PerfFormattedData_Tcpip_NetworkInterface",
    "Win32_PerfFormattedData_Tcpip_TCP",
    "Win32_PerfFormattedData_Tcpip_UDP",
    "Win32_PerfFormattedData_TermService_TerminalServices",
    "Win32_PerfFormattedData_TermService_TerminalServicesSession",
    "Win32_PerfFormattedData_W3SVC_WebService",
    "Win32_PerfRawData",
    "Win32_PerfRawData_ASP_ActiveServerPages",
    "Win32_PerfRawData_ContentFilter_IndexingServiceFilter",
    "Win32_PerfRawData_ContentIndex_IndexingService",
    "Win32_PerfRawData_InetInfo_InternetInformationServicesGlobal",
    "Win32_PerfRawData_ISAPISearch_HttpIndexingService",
    "Win32_PerfRawData_MSDTC_DistributedTransactionCoordinator",
    "Win32_PerfRawData_NTFSDRV_SMTPNTFSStoreDriver",
    "Win32_PerfRawData_PerfDisk_LogicalDisk",
    "Win32_PerfRawData_PerfDisk_PhysicalDisk",
    "Win32_PerfRawData_PerfNet_Browser",
    "Win32_PerfRawData_PerfNet_Redirector",
    "Win32_PerfRawData_PerfNet_Server",
    "Win32_PerfRawData_PerfNet_ServerWorkQueues",
    "Win32_PerfRawData_PerfOS_Cache",
    "Win32_PerfRawData_PerfOS_Memory",
    "Win32_PerfRawData_PerfOS_Objects",
    "Win32_PerfRawData_PerfOS_PagingFile",
    "Win32_PerfRawData_PerfOS_Processor",
    "Win32_PerfRawData_PerfOS_System",
    "Win32_PerfRawData_PerfProc_FullImage_Costly",
    "Win32_PerfRawData_PerfProc_Image_Costly",
    "Win32_PerfRawData_PerfProc_JobObject",
    "Win32_PerfRawData_PerfProc_JobObjectDetails",
    "Win32_PerfRawData_PerfProc_Process",
    "Win32_PerfRawData_PerfProc_ProcessAddressSpace_Costly",
    "Win32_PerfRawData_PerfProc_Thread",
    "Win32_PerfRawData_PerfProc_ThreadDetails_Costly",
    "Win32_PerfRawData_PSched_PSchedFlow",
    "Win32_PerfRawData_PSched_PSchedPipe",
    "Win32_PerfRawData_RemoteAccess_RASPort",
    "Win32_PerfRawData_RemoteAccess_RASTotal",
    "Win32_PerfRawData_RSVP_ACSRSVPInterfaces",
    "Win32_PerfRawData_RSVP_ACSRSVPService",
    "Win32_PerfRawData_SMTPSVC_SMTPServer",
    "Win32_PerfRawData_Spooler_PrintQueue",
    "Win32_PerfRawData_TapiSrv_Telephony",
    "Win32_PerfRawData_Tcpip_ICMP",
    "Win32_PerfRawData_Tcpip_IP",
    "Win32_PerfRawData_Tcpip_NBTConnection",
    "Win32_PerfRawData_Tcpip_NetworkInterface",
    "Win32_PerfRawData_Tcpip_TCP",
    "Win32_PerfRawData_Tcpip_UDP",
    "Win32_PerfRawData_TermService_TerminalServices",
    "Win32_PerfRawData_TermService_TerminalServicesSession",
    "Win32_PerfRawData_W3SVC_WebService",
    "Win32_PhysicalMedia",
    "Win32_PhysicalMemory",
    "Win32_PhysicalMemoryArray",
    "Win32_PhysicalMemoryLocation",
    "Win32_PingStatus",
    "Win32_PnPAllocatedResource",
    "Win32_PnPDevice",
    "Win32_PnPEntity",
    "Win32_PnPSignedDriver",
    "Win32_PnPSignedDriverCIMDataFile",
    "Win32_PointingDevice",
    "Win32_PortableBattery",
    "Win32_PortConnector",
    "Win32_PortResource",
    "Win32_POTSModem",
    "Win32_POTSModemToSerialPort",
    "Win32_PowerManagementEvent",
    "Win32_Printer",
    "Win32_PrinterConfiguration",
    "Win32_PrinterController",
    "Win32_PrinterDriver",
    "Win32_PrinterDriverDll",
    "Win32_PrinterSetting",
    "Win32_PrinterShare",
    "Win32_PrintJob",
    "Win32_PrivilegesStatus",
    "Win32_Process",
    "Win32_Processor",
    "Win32_ProcessStartTrace",
    "Win32_ProcessStartup",
    "Win32_ProcessStopTrace",
    "Win32_ProcessTrace",
    "Win32_Product",
    "Win32_ProductCheck",
    "Win32_ProductResource",
    "Win32_ProductSoftwareFeatures",
    "Win32_ProgIDSpecification",
    "Win32_ProgramGroup",
    "Win32_ProgramGroupContents",
    "Win32_ProgramGroupOrItem",
    "Win32_Property",
    "Win32_ProtocolBinding",
    "Win32_Proxy",
    "Win32_PublishComponentAction",
    "Win32_QuickFixEngineering",
    "Win32_QuotaSetting",
    "Win32_Refrigeration",
    "Win32_Registry",
    "Win32_RegistryAction",
    "Win32_RemoveFileAction",
    "Win32_RemoveIniAction",
    "Win32_ReserveCost",
    "Win32_ScheduledJob",
    "Win32_SCSIController",
    "Win32_SCSIControllerDevice",
    "Win32_SecurityDescriptor",
    "Win32_SecurityDescriptorHelper",
    "Win32_SecuritySetting",
    "Win32_SecuritySettingAccess",
    "Win32_SecuritySettingAuditing",
    "Win32_SecuritySettingGroup",
    "Win32_SecuritySettingOfLogicalFile",
    "Win32_SecuritySettingOfLogicalShare",
    "Win32_SecuritySettingOfObject",
    "Win32_SecuritySettingOwner",
    "Win32_SelfRegModuleAction",
    "Win32_SerialPort",
    "Win32_SerialPortConfiguration",
    "Win32_SerialPortSetting",
    "Win32_ServerConnection",
    "Win32_ServerFeature",
    "Win32_ServerSession",
    "Win32_Service",
    "Win32_ServiceControl",
    "Win32_ServiceSpecification",
    "Win32_ServiceSpecificationService",
    "Win32_Session",
    "Win32_SessionConnection",
    "Win32_SessionProcess",
    "Win32_SettingCheck",
    "Win32_ShadowBy",
    "Win32_ShadowContext",
    "Win32_ShadowCopy",
    "Win32_ShadowDiffVolumeSupport",
    "Win32_ShadowFor",
    "Win32_ShadowOn",
    "Win32_ShadowProvider",
    "Win32_ShadowStorage",
    "Win32_ShadowVolumeSupport",
    "Win32_Share",
    "Win32_ShareToDirectory",
    "Win32_ShortcutAction",
    "Win32_ShortcutFile",
    "Win32_ShortcutSAP",
    "Win32_SID",
    "Win32_SIDandAttributes",
    "Win32_SMBIOSMemory",
    "Win32_SoftwareElement",
    "Win32_SoftwareElementAction",
    "Win32_SoftwareElementCheck",
    "Win32_SoftwareElementCondition",
    "Win32_SoftwareElementResource",
    "Win32_SoftwareFeature",
    "Win32_SoftwareFeatureAction",
    "Win32_SoftwareFeatureCheck",
    "Win32_SoftwareFeatureParent",
    "Win32_SoftwareFeatureSoftwareElements",
    "Win32_SoundDevice",
    "Win32_StartupCommand",
    "Win32_SubDirectory",
    "Win32_SystemAccount",
    "Win32_SystemBIOS",
    "Win32_SystemBootConfiguration",
    "Win32_SystemConfigurationChangeEvent",
    "Win32_SystemDesktop",
    "Win32_SystemDevices",
    "Win32_SystemDriver",
    "Win32_SystemDriverPnPEntity",
    "Win32_SystemEnclosure",
    "Win32_SystemLoadOrderGroups",
    "Win32_SystemLogicalMemoryConfiguration",
    "Win32_SystemMemoryResource",
    "Win32_SystemNetworkConnections",
    "Win32_SystemOperatingSystem",
    "Win32_SystemPartitions",
    "Win32_SystemProcesses",
    "Win32_SystemProgramGroups",
    "Win32_SystemResources",
    "Win32_SystemServices",
    "Win32_SystemSetting",
    "Win32_SystemSlot",
    "Win32_SystemSystemDriver",
    "Win32_SystemTimeZone",
    "Win32_SystemTrace",
    "Win32_SystemUsers",
    "Win32_TapeDrive",
    "Win32_TCPIPPrinterPort",
    "Win32_TemperatureProbe",
    "Win32_Thread",
    "Win32_ThreadStartTrace",
    "Win32_ThreadStopTrace",
    "Win32_ThreadTrace",
    "Win32_TimeZone",
    "Win32_TokenGroups",
    "Win32_TokenPrivileges",
    "Win32_Trustee",
    "Win32_TypeLibraryAction",
    "Win32_UninterruptiblePowerSupply",
    "Win32_USBController",
    "Win32_USBControllerDevice",
    "Win32_USBHub",
    "Win32_UserAccount",
    "Win32_UserDesktop",
    "Win32_UserInDomain",
    "Win32_UTCTime",
    "Win32_VideoConfiguration",
    "Win32_VideoController",
    "Win32_VideoSettings",
    "Win32_VoltageProbe",
    "Win32_Volume",
    "Win32_VolumeChangeEvent",
    "Win32_VolumeQuota",
    "Win32_VolumeQuotaSetting",
    "Win32_VolumeUserQuota",
    "Win32_WindowsProductActivation",
    "Win32_WMIElementSetting",
    "Win32_WMISetting"
};

Other than attaching the results of the WMI query to a generic list and binding to a DataList, that’s pretty much it.  The end result is a web page that you can use to view system details via WMI:

image

As I’ve said, this is a very simple demonstration of WMI in Windows Azure.  What would be more interesting is to see how someone might combine the ability to spin up asynchronous processes in Windows Azure (as described in my post How to Leverage the RoleEntryPoint in an Azure Web Role) that query data via WMI and centralize it in Azure storage (i.e. combining and centralizing all event logs) – that would be very cool!

In case anyone wants to take a look, I’ve uploaded the code to SkyDrive.

Enjoy!

Webcast: Running an ASP.NET MVC Web Application in Windows Azure

Posted by Wade on October 11, 2009 | 5 comments

I have seen enough people ask about running ASP.NET MVC Web Applications in Windows Azure that I thought I’d put together a short, quick webcast that shows exactly the steps you need to take.  With the further ado …

Before you try this yourself, make sure you satisfy the following dependencies:

For those of you that have no desire to watch a four minute video, and would rather have a quick walkthrough, here you go:

  1. Create a blank cloud services project.  Do not add any roles to the project.
  2. Add a new ASP.NET MVC Web Application to the solution.
  3. Add  the ASP.NET MVC Web Application as a web role in the cloud services project.
  4. Add the Microsoft.ServiceHosting.ServiceRuntime.dll assembly to the ASP.NET MVC Web Application.
  5. Set the following MVC assemblies to Copy Local True.
    • System.Web.Abstractions
    • System.Web.Mvc
    • System.Routing
  6. Run the application.

I hope this helps!

Passive Federation with Windows Azure and ADFS v2 (codenamed "Geneva" Server)

Posted by Wade on October 9, 2009 | 3 comments

One of the critical elements a company needs to consider when moving to the cloud is how they will leverage their existing identity stores.  Most companies have made significant investments in various identity solutions (i.e. providing for SSO, identity consolidation, federating with partners, etc.) and it’s imperative to ensure that applications and services in the cloud can take advantage of these resources.

Background

The practice of enabling the portability of identity information across otherwise autonomous security domains, called identity federation, is no longer a luxury – it’s a necessity.  The inability to allow users to access resources in different datacenters, with various trading partners, or on the Web, can quickly cripple a companies productivity (not to mention user satisfaction).  Historically, providing for the needs of Web-based single sign on (SSO) and cross-domain resource access has been very difficult to accomplish.  It may have required the replication of identity stores in a host of one-off scenario, or even (gasp!) compromising security best practices in order to satisfy a business need.

More recently, various kinds of identity architecture have made identity federation much easier.  Practices like claims-based authentication, standards like WS-Federation, and so forth allow companies to establish trust domains between different organization and parties much easier.  See the following illustration from InfoQ:

Claims-based identity with tokens

In this picture, the application implicitly trusts tokens that come from the issuer.  Consequently, rather than having to authenticate the users identity, the application can delegate that responsibility to the issuer, and instead focus on parsing the information received in the token.  These tokens contain claims, which are nothing more than bits of information about a person, such as a name, email address, membership in a particular role, and so on.  If your application trusts the issuer of the claim, you can leverage the information without having to query the identity store directly.

While it is possible to build a customer claims issuer (or more appropriately called a security token service), there are a host of solutions available today that make this quite easy.  Microsoft’s Active Directory Federation Services (ADFS) is a great example, and work implicitly with identities stored in active directory.  ADFS allows for federated identity by implementing claims-based authentication and establishing trust between different organizations and parties.

ADFS v2 (which was previously codenamed "Geneva" Server") expands this capability by bringing claims-based identity federation to cloud-based applications that live in on the web, in the enterprise, or across an organization.  The "Geneva" Beta 2 datasheet provides the following description that I think is a great summation of ADFS v2:

“Geneva” is Microsoft’s next generation identity and access management platform built on Active Directory® directory services. “Geneva” provides claims-based access and single sign-on for on-premises and cloud-based applications in the enterprise, across organizations, and on the Web.

“Geneva” leverages claims which describe identity attributes and can be used to drive application and other system behaviors with an open architecture that implements the industry’s shared Identity Metasystem vision.

A few things I’d like to point out about ADFS v2 that make it very powerful:

  • Open standards.  ADFS v2 is based on SAML 2.0, so it can interoperate with products from all kinds of vendors and platforms.
  • Automation.  ADFS v2 handles the federation of identities by setting up the trust relationships.
  • Developer productivity.  Since developers interact with trusted claims, they don’t have to spend time worry about the particulars of the identity provider.

In this post, I want to highlight how you can use ADFS v2 to solve many of these identity challenges as you move to the cloud.  Additionally, I will also highlight how the Windows Identity Foundation (previously codenamed "Geneva" Framework), or WIF, is used to make your applications claims-aware and minimize the amount of work required by developers.

To explore ADFS v2 and WIF in more detail – and I strongly encourage you do! – please take a look at the following resources and blogs:

Now, before you go through this post and start building this solution for yourself, download the Windows Identity Foundation and Windows Azure Passive Federation code.  Run the executable; immediately after the code is installed/copied on to your machine a browser window will open and load the documentation.  Read the entire guide.  There is a ton of good information in the documentation.  Next, run through the whole guide and run the demos.  I’m not going to restate anything mentioned in the guide in this post, since the guide does a fabulous job.  This post assumes that you’ve gone through and leveraged the WIF & WA passive federation guide.

A few additional assumptions I’d like to call out:

  • You have an ADFS v2 server (today you’d use "Geneva" Server Beta 2).
  • You have installed Windows Identity Foundation.
  • You are running Visual Studio 2008 (although 2010 will probably work fine).
  • You have installed the Windows Azure SDK and Windows Azure Tools for Visual Studio.
  • You have a Windows Azure account and project.

Okay, let’s get started!

Building the Application

For a comprehensive examination of many of the below steps, please refer to the overview and walkthrough installed in the guide you installed above.  For the sake of brevity, I have focused on the execution of the process rather than a thorough explanation of how it works.  The guide provides a lot of valuable information.

1. To start, create your Windows Azure project.  I created a "Federated Identity Demo" project with the service name "fedid".  Be sure and keep track of the service name you choose, as you will use it when creating your certificate.

2. You must now create the certificate that you’ll use for SSL.  Open the folder where you installed the WIF & WA federation guide.  In this folder you will find a folder called assets.  Open a Visual Studio 2008 Command Prompt (choose to runas administrator) and run the command:

CreateCert.cmd <Azure Project service name>

For example, this is what I ran:

CreateCert.cmd

Note: you will have to enter the password "abc!123" multiple times.  This is documented in the guide.  Additionally, you will be prompted to install a certificate on your machine – click Yes.

Certification Authority

This process will create a number of files in the Assets folder, including:

  • fedid.cloudapp.net.cer
  • fedid.cloudapp.net.pfx
  • fedid.cloudapp.net.pvk
  • encoder.out

3. Create a new Visual Studio 2008 solution.  Add a Cloud Service project, and create a Web Role.

4. Update the ServiceDefinition.csdef file to update the following:

  • HTTPS protocol
  • Port 443
  • enableNativeCodeExecution: true

It should look like the following after the update:

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="CloudService" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="Web" enableNativeCodeExecution="true">
    <InputEndpoints>
      <!-- Must use port 80 for http and port 443 for https when running in the cloud -->
      <InputEndpoint name="HttpIn" protocol="https" port="443" />
    </InputEndpoints>
    <ConfigurationSettings />
  </WebRole>
</ServiceDefinition>

5. Right-click the Cloud Service project, and choose Properties.  Select the SSL tab.  Enable both checkboxes, and select the certificate you generated from the store.  Here’s what it looks like for me once I finished updating:

 SSL tab 

6. Add the following references to your Web Role project:

  • System.IdentityModel
  • Microsoft.IdentityModel
  • Microsoft.IdentityModelPlus (this is in the assets folder that was created by the guide)

Make sure to set Copy Local true for Microsoft.IdentityModel and Microsoft.IdentityModelPlus so that they are added to your Windows Azure package.

7. Create a Global.asax file.

8. Update the Global.asax file so that you have the following using statements:

using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using Microsoft.IdentityModel;
using Microsoft.IdentityModel.Configuration;
using Microsoft.IdentityModel.Tokens;
using Microsoft.IdentityModel.Web;
using Microsoft.IdentityModel.Web.Configuration;
using Microsoft.IdentityModelPlus.Configuration;
using Microsoft.IdentityModelPlus.Tokens;

9. Add the following "ServiceConfiguration_Created" method:

void ServiceConfiguration_Created(object sender, ServiceConfigurationCreatedEventArgs args)
{
    ServiceConfiguration configuration = args.ServiceConfiguration;
    List<CookieTransform> sessionTransforms = new List<CookieTransform>(new CookieTransform[]
    {
        new DeflateCookieTransform(), new MachineKeyProtectionTransform()
    });
    SessionSecurityTokenHandler sessionHandler = new SessionSecurityTokenHandler(sessionTransforms.AsReadOnly(), new MruSecurityTokenCache());
    configuration.SecurityTokenHandlers.AddOrReplace(sessionHandler);
    MicrosoftIdentityModelPlusSection plusConfiguration = MicrosoftIdentityModelPlusSection.Current;

    if (plusConfiguration != null && plusConfiguration.ServiceCertificate.ElementInformation.IsPresent)
    {
        X509Certificate2 serviceCertificate = plusConfiguration.ServiceCertificate.GetCertificate();
        SecurityToken serviceToken = new X509SecurityToken(serviceCertificate);
        SecurityTokenResolver serviceResolver = SecurityTokenResolver.CreateDefaultSecurityTokenResolver(
            new List<SecurityToken>(new SecurityToken[] { serviceToken }).AsReadOnly(), false);

        configuration.ServiceTokenResolver = serviceResolver;
    }
}

10. Add the following "WSFederationAuthenticationModule_RedirectingToIdentityProvider" method:

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(object sender, RedirectingToIdentityProviderEventArgs e)
{
    Uri reqUrl = Request.Url;
    StringBuilder wreply = new StringBuilder();
    wreply.Append(reqUrl.Scheme);     // e.g. "http"
    wreply.Append("://");
    wreply.Append(Request.Headers["Host"] ?? reqUrl.Authority);
    wreply.Append(Request.ApplicationPath);
    if (!Request.ApplicationPath.EndsWith("/"))
    {
        wreply.Append("/");
    }
    e.SignInRequestMessage.Reply = wreply.ToString();
}

11. Update the "Application_Start" method so that it creates event handlers for the two previous methods:

protected void Application_Start(object sender, EventArgs e)
{
    FederatedAuthentication.ServiceConfigurationCreated += this.ServiceConfiguration_Created;

    FederatedAuthentication.WSFederationAuthenticationModule.RedirectingToIdentityProvider +=
        new EventHandler<RedirectingToIdentityProviderEventArgs>(WSFederationAuthenticationModule_RedirectingToIdentityProvider);
}

12.  You must now make some significant updates to the Web.Config file.  This is probably the most challenging step, as it requires a number of updates.  While I will walk through the details of the updates below, I am also including a copy of my Web.Config file for your review on SkyDrive.  Please feel free to use it as a reference.  Also, please note that (for simplicity) the code shown below includes values that my demo uses.  You will have to update with your own values, and I have tried to call out where this is necessary.

13. Add the following to the <configuration><configSections> … </configSections></configuration>

<section name="microsoft.identityModel" type="Microsoft.IdentityModel.Configuration.MicrosoftIdentityModelSection, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="microsoft.identityModelPlus" type="Microsoft.IdentityModelPlus.Configuration.MicrosoftIdentityModelPlusSection, Microsoft.IdentityModelPlus" requirePermission="false" />

14. Add the app key for the federation metadata location.  You will have to update this value with your own XML file published by your ADFS v2 server.

<appSettings>
  <add key="FederationMetadataLocation" value="https://corp2.sts.microsoft.com/FederationMetadata/2007-06/FederationMetadata.xml" />
</appSettings>

15. After connectionStrings, add the following location path.

<location path="FederationMetadata">
  <system.web>
    <authorization>
      <allow users="*" />
    </authorization>
  </system.web>
</location>

16. Add the Microsoft.IdentityModel assembly.

<add assembly="Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

17. Configure the authorization and authentication.  You may have to change existing values.

<authorization>
  <deny users="?" />
</authorization>
<authentication mode="None" />

18. Add the WSFederationAuthenticationModule and SessionAuthenticationModule modules to <httpModules> … </httpModules>.

<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

19. Add the WSFederationAuthenticationModule and SessionAuthenticationModule modules to <modules> … </modules>.

<add name="WSFederationAuthenticationModule" type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />
<add name="SessionAuthenticationModule" type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" preCondition="managedHandler" />

20. Add the microsoft.identityModel section.

<microsoft.identityModel>
  <service>
    <audienceUris>
      <add value="https://fedid.cloudapp.net/" />
    </audienceUris>
    <federatedAuthentication>
      <wsFederation passiveRedirectEnabled="true" issuer="https://corp2.sts.microsoft.com/FederationPassive/" realm="https://fedid.cloudapp.net/" requireHttps="true" />
      <cookieHandler requireSsl="true" />
    </federatedAuthentication>
    <applicationService>
      <claimTypeRequired>
        <claimType type="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name" optional="true" />
        <claimType type="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" optional="true" />
      </claimTypeRequired>
    </applicationService>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=0.6.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <trustedIssuers>
        <add thumbprint="A4010FC094ECEDE6C94EDE36315ADB3EEC876C8A" name="CN=corp2.sts.microsoft.com, OU=IAM, O=Microsoft, L=Redmond, S=wa, C=US" />
      </trustedIssuers>
    </issuerNameRegistry>
  </service>
</microsoft.identityModel>

You will have to make a number of updates:

  • Update "fedid" in "https://fedid.cloudapp.net" to your Windows Azure service name.
  • Update the wsFederation issuer to your ADFS v2 issuer.
  • Update the wsFederation realm so that it uses your Windows Azure service name.
  • Ensure that the claimTypeRequired claimTypes are valid.
  • Update the trustedIssures so that you have your ADFS v2 thumbprint and name.  I did this by using tools that come with the Windows Identity Foundation tools.  You can right-click an existing ASP.NET Web site project and select "Modify STS Reference…".  This will launch the Federation Utility wizard, and will help construct pieces of the Web.Config.  On the second step, you can select "Use an Existing STS" and point it to your ADFS v2 endpoint.  The tool will then generate a lot of the configuration for you, including the trustedIssuers thumbprint and name (see the image below).

Federation Utility wizard

Note: this doesn’t need to be a publicly exposed ADFS server.  In fact, if you don’t have a production IP address to use, you can download one sample (simple) IP STS included in the code downloaded from http://claimsid.codeplex.com/.

21. You need to add the Microsoft.IdentityModelPlus section to the Web.Config file.  The CreateCert.cmd script you ran to generate the script has actually generated this section for you.  You can get it out of the encoder.out file in the assets folder.

<microsoft.identityModelPlus><serviceCertificate><certificate name="fedid.cloudapp.net" password="abc!123" encodedType="pfx" encodedValue="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" /></serviceCertificate></microsoft.identityModelPlus>

Note: this is only necessary because currently you cannot install certificates in Windows Azure.  Expect this to change in the future.

22. Finally, as a way of confirming that everything is working and that the claims you expect are actually sent to your application, update the Default.aspx.cs file so that you iterate through the claims and write them to the browser.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.IdentityModel.Claims;
using System.Threading;

namespace Web
{
    public partial class _Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            try
            {
                IClaimsIdentity ici =
                    Thread.CurrentPrincipal.Identity as IClaimsIdentity;

                foreach (Claim c in ici.Claims)
                    Response.Write(c.ClaimType + " - " + c.Value + "<br/>");
            }
            catch (Exception ex)
            {
                Response.Write(ex.ToString());
            }
        }
    }
}

And that’s it!  Assuming that you have correctly followed the above steps, you’re ready to test!

Now, when you hit the endpoint on your ADFS v2 service, you will get redirected to https://fedid.cloudapp.net (or whatever your URL is) because that’s what you specified as the realm for your identity federation.  This means that, to test this locally, you need to update your hosts file so that fedid.cloudapp.net temporarily resolves locally.  Otherwise, it will redirect to the cloud.  I know we’ve spend years abhorring the hosts file, but sometimes it just works.

hosts

When you’re ready to run this in the cloud, simply package up your Windows Azure solution and publish.  Remember that you will only be able to successfully test this in production, as the staging account uses a GUID for the URL alias.  Also, if you’ve updated your hosts file, but sure and undo your changes so that you no longer resolve locally.

I hope you found this article valuable.  Good luck!

Using the UpdateProgress to lock down controls in the browser

Posted by Wade on January 7, 2008 | 6 comments

Finally, back to some writing some code!  Between writing my book and some of my more recent projects, I haven’t had a chance to write a lot of code.

I’m currently porting a web-based timesheet application to an ASP.NET AJAX Futures Web Application with the .NET Framework 3.5.  In writing the application, I’m trying to adhere to a number of practices:

  1. Separation of concerns
  2. Test driven development (TDD) to support changing business requirements
  3. Intuitive and fool proof user interface

It’s because of #3 that I am going with an ASP.NET AJAX application.

ASP.NET AJAX uses an UpdatePanel control to support partial-page updates; essentially, controls contained within the ContentTemplate property can be updated without refreshing the entire page.  The ASP.NET WebForm also contains a ScriptManager control which allows the UpdatePanel to participate in partial-page updates without requiring custom client script code.

The following code shows these controls working together:

<asp:ScriptManager ID="ScriptManager1" runat="server" />
<div id="main">
    <asp:UpdatePanel ID="UpdatePanel1" runat="server">
        <ContentTemplate>
            <asp:Button ID="Button1" runat="server" Text="Button" OnClick="Button1_Click" />
        </ContentTemplate>
    </asp:UpdatePanel>
</div>

Pretty simple.

When the button is clicked, the page does not appear to postback; instead, the button click invokes an asynchronous postback in which the page updates are limited to the controls in the UpdatePanel.  The server sends back HTML markup for only the affected elements to the browser.  Within the browser, the client PageRequestManager class performs Document Object Model (DOM) manipulation to replace existing HTML with updated markup.

Simple, but very cool stuff!  What’s nice with ASP.NET 3.5 and Visual Studio 2008 is that you don’t have to install anything else in order to build ASP.NET AJAX applications — it’s already built in!

Now, if it takes awhile for the server to process the postback (e.g. complex rules or badly written code <grin>), the user may not realize that the server is processing the request.  This can lead to all kinds of issues with users that are not savvy or familiar with web applications (multiple clicks, moving off the page, etc.).  Consequently, I want to tell the user that the server is processing the request and disable the controls on the page.  Let’s break this down into two steps: show a message, and disable the user’s interaction with the controls.

You can use the UpdateProgress control alone with the UpdatePanel to provide a message to the user during the postback.  This is very simple — put the UpdateProgress control within the UpdatePanel like so:

<asp:UpdateProgress ID="UpdateProgress1" runat="server">
    <ProgressTemplate>
        Update in progress. Please wait ...
    </ProgressTemplate>
</asp:UpdateProgress>

This will display the "Update in progress.  Please wait …" message to the the user while the server is processing the request.  However, it doesn’t prevent the user from continuing to interact with the web application.  To provide this type of functionality, we will use the PageRequestManager to invoke some JavaScript while also using CSS and DHTML to lock down the UI.

First, we’ll add a little more to our UpdateProgress control:

<ProgressTemplate>
    <div id="blur">&nbsp;</div>
    <div id="progress">
        Update in progress. Please wait ...
    </div>
</ProgressTemplate>

We’ll use the "blur" and "progress" controls to overlay the controls in the UI while also providing a message to the user.  To provide the functionality we require, we need to use the following CSS elements:

#blur
{
    width: 100%;
    background-color: black;
    moz-opacity: 0.5;
    khtml-opacity: .5;
    opacity: .5;
    filter: alpha(opacity=50);
    z-index: 120;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
}
#progress
{
    z-index: 200;
    background-color: White;
    position: absolute;
    top: 0pt;
    left: 0pt;
    border: solid 1px black;
    padding: 5px 5px 5px 5px;
    text-align: center;
}

The purpose of the "blur" control is to provide a tag that lays over everything in the browser.  Since the opacity is 0.5 (and 50), it appears gray while allowing the user to continue to see the controls behind it.  However, since the "blur" control exists between the user and the other controls, the user cannot interact with any other controls.

Now, the tricky thing is that we need to run a JavaScript function to manipulate the size and positioning of the "blur" and "progress" controls; essentially, we want the "blur" to cover 100% of the browser window, and the "progress" to sit in a box in the center.  The key part is hooking in a JavaScript call to the initialization of the PageRequestManager request.  To do this, you can add the following JavaScript after the ScriptManager:

<asp:ScriptManager ID="ScriptManager1" runat="server" />
<script language="javascript" type="text/javascript">
<!--
    Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(
    function () {
        // code here
    )
// -->
</script>

This JavaScript allows you to add JavaScript code that will process during the initialization of the postback — a perfect place for us to grab the information we need.  Here’s what the complete code looks like:

Sys.WebForms.PageRequestManager.getInstance().add_initializeRequest(
function () {
    if (document.getElementById) {
        var progress = document.getElementById('progress');
        var blur = document.getElementById('blur');

        progress.style.width = '300px';
        progress.style.height = '30px';

        blur.style.height = document.documentElement.clientHeight;
        progress.style.top = document.documentElement.clientHeight/3 - progress.style.height.replace('px','')/2 + 'px';
        progress.style.left = document.body.offsetWidth/2 - progress.style.width.replace('px','')/2 + 'px';
    }
  }
)

The exact implementation isn’t that important — what I think is important is that you can hook into the PageRequestManager to invoke some JavaScript.

Now, to help test, you can use System.Threading to make the button click sleep for two seconds:

protected void Button1_Click(object sender, EventArgs e)
{
    System.Threading.Thread.Sleep(2000);
}

Okay, now to test.  Here’s the page prior to the postback:

image

Once the page is clicked, the experience changes to the following while the server is processing the request:

image

And yes, this works in Firefox too:

image

As I mentioned before, the "blur" and "progress" controls act as a screen over all the other controls, and since they are part of the ProgressTemplate these controls they are only active during server processing.  You can confirm this by removing the Sleep method — you won’t even see you the "blur" and "progress" controls, as they are not needed.

This is only one little part of what I’m trying to do with this new application — hopefully you find it interesting!  Of course, I hope you can make it all look a little prettier than what I threw together for this post!

I hope this helps!