Welcome to MSDN Blogs Sign in | Join | Help

Applications Relying on GetProfileString Function or VB.Global.Printers for Printer Enumeration May Experience Performance Issues on Windows Server 2008 Terminal Server

The VB6.0 Runtime is continued to be supported in Windows Server 2008 and Windows 7. Consequently, many ISVs continue to deploy VB6.0 applications to the new operating systems. I would like to comment possible performance issue that you may encounter in Terminal Server 2008 if you deploy a an application to TS users. The issue here is currently under investigation and treated as a bug. My intention is to give you some more information about it before we have official statement from the product group on it. In particular, if you have source code of the application you could work around the issue. Note, that the information below is based on my practical experience where we could help customers experiencing the issue by pointing them to the recommended API. In a nutshell if possible re-factor the code substituting the GetProfileSeting() call and Printers object with the result of EnumPrinters function call.

I will post an update once product group completes its investigation, for the time being here are the details:

Symptoms:

If the following conditions hold:

-You have deployed an application that calls GetProfileString or makes use of VB6.0 Printers collection object to Windows Terminal Server 2008 or later

and

-You have enabled Easy Print feature in the Terminal Server

then

your application can experience performance issues in the printer enumeration routine because of the increasing size of the Printers collection over time.

Additionally you observe large number of entries accumulating in the HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices registry hive.

More Information

Some legacy  applications often rely on the Vb.Global.Printers (PrinterCollection) collection object or GetProfileString() function to enumerate printer objects. The Printers object utilizes a function call GetProfileString() that in turn reads information from the HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices registry hive. As stated in the documentation GetProfileString() should not be called from server side code.

The registry key accumulation under HKCU\Software\Microsoft\Windows NT\CurrentVersion\Devices that is the root cause of the issue reported there is currently treated as bug and we are working on resolving this issue.

Workaround:

It is recommended that the Winspool API function EnumPrinters is used as a mechanism to obtain a list of user printers in terminal session instead of relying on the Printers object. If the source code is available, references to Printers collection should be substituted with the call to the EnumPrinters function. Performance of this function will vary depending on the type PRINTER_INFO structure returned as described in the Remarks section of the referenced document below.

References

EnumPrinters Function

http://msdn.microsoft.com/en-us/library/dd162692(VS.85).aspx

ACC: Enumerating Local and Network Printers

http://support.microsoft.com/default.aspx/kb/166008

GetProfileString Function

http://msdn.microsoft.com/en-us/library/ms724366(VS.85).aspx

Printer Object, Printers Collection

http://msdn.microsoft.com/en-us/library/aa267233(VS.60).aspx

Introducing Terminal Services Easy Print: Part 2-Per Session Default Printers

http://blogs.msdn.com/rds/archive/2007/05/03/introducing-terminal-services-easy-print-part-2.aspx

Applies to:

Windows Server 2008 Terminal Server

Windows Server 2008 R2 Terminal Server

Visual Basic 6.0

Posted by micham | 0 Comments

ASP.Net MVC 1.0 RTM

ASP.Net MVC (http://msdn.microsoft.com/en-us/library/dd394709.aspx) has reached the RTM stage on 18 of March. It is an ecitin alternative to the “classic” ASP.Net Web Forms model. Framework 3.5. The separation of responsibilities between the components of this framework makes is easier to apply Test Driven Development software approach and work in larger teams of designers and developers.   Other benefits include the friendly REST style URLs that users see when interacting with the application and ability to have full control over the HTML rendered to the browsers.

You may be interested to review the references for more information:

•ASP.Net Routing http://msdn.microsoft.com/en-us/library/cc668201.aspx

•MVC Tutorials http://www.asp.net/learn/mvc/

•Building Web Apps without Web Forms http://msdn.microsoft.com/en-us/magazine/cc337884.aspx

•ASP.NET MVC 1.0 Release Candidate Now Available http://weblogs.asp.net/scottgu/archive/2009/01/27/asp-net-mvc-1-0-release-candidate-now-available.aspx

•MSDN Webcast: ASP.NET Model View Controller Framework Overview (Level 200)

http://msevents.microsoft.com/CUI/WebCastEventDetails.aspx?EventID=1032400599&EventCategory=5&culture=en-US&CountryCode=US

I have delivered the presentation to Spanish ISV Gold Partners on the Partner Learning Center. If you are a Gold Partner you should be able to access the recording at https://training.partner.microsoft.com/plc/details.aspx?publisher=12&delivery=265304. Below you will find the English version of the presentation that introduces main concepts of this interesting framework.

Posted by micham | 1 Comments

Windows Workflow Foundation 4.0 and “Dublin”

If you develop with Microsoft Workflow Foundation 3.5 you will be interested in upcoming “Dublin” technology. Essentially, “Dublin” is a workflow engine (“process host” that currently you have to code yourself) integrated into the operating system. It also provides services for durable, persisted workflows and improved management of workflow instances leveraging familiar IIS7 management console.

Following whitepaper comments the improvements in WCF and WF being prepared for .Net Framework 4.0 and introduces “Dublin”:

WCF And WF Services In The .NET Framework 4.0 And “Dublin”

http://msdn.microsoft.com/en-us/magazine/2009.01.net40.aspx

More detail including technology demonstrations can be found in Professional Developer Conference 2008 talks:

WF 4.0: A First Look

Learn how programming with Windows Workflow Foundation (WF) 4.0 provides clarity of intent while preserving the functional richness of the .NET framework.

Webcast-Online

"Dublin": Hosting and Managing Workflows and Services in Windows Application Server

Hear about extensions being made to Windows Server to provide a feature-rich middle-tier execution and deployment environment for Windows Workflow Foundation (WF) and Windows Communication Foundation (WCF) applications.

Webcast-Online

The  overview of “Dublin” features and a mini FAQ for this technology can be found in

Riding the Next Platform Wave: Building and Managing Composite Applications

http://download.microsoft.com/download/5/9/B/59B74A2A-245D-4304-802E-E0A0800FACD3/Dublin__NET_4_overview.docx

And for architectural overview of the relationship of “Dublin” to “Oslo” modeling and WWF4.0 check

Creating Modern Applications: Workflows, Services, and Models

http://msdn.microsoft.com/en-us/library/dd200919.aspx

Posted by micham | 0 Comments
Filed under: ,

Visual Studio 2010 and .Net Framework 4.0 CTP

You can get a peek at the next version of Visual Studio and .Net Framework 4.0 by using following resources:

 

Register for the “Visual Studio and .Net Framework” Connection on Microsoft Connect”

https://connect.microsoft.com/VisualStudio/content/content.aspx?ContentID=9790

- This gives you access to updates, info about patches available etc.

 

Bookmark the 10-4 Channel 9 area dedicated to VS2010 and .Net 4.0, check out the videos available

http://channel9.msdn.com/shows/10-4/

 

Download the Virtual PC images of Visual Studio 2010 (prof or team suite)

“Microsoft Pre-release Software Visual Studio 2010 and .NET Framework 4.0 Community Technology Preview (CTP)”

https://www.microsoft.com/downloads/details.aspx?familyid=922b4655-93d0-4476-bda4-94cf5f8d4814&displaylang=en

 

Download the Training Kit- includes demo scripts, presentations and hands-on labs

“Visual Studio 2010 and .NET Framework 4.0 Training Kit - November Preview”

http://www.microsoft.com/downloads/details.aspx?FamilyId=752CB725-969B-4732-A383-ED5740F02E93&displaylang=en

Posted by micham | 0 Comments

How to return uniqueidentifier initialized with NEWSEQUENTIALID() function to the .Net client

Problem statement:

The uniqueidentifier SQL Server data type is increasingly used in applications that require global uniqueness of the primary key. In SQL Server 2005 NEWSEQUENTIALID() function has been introduced that generates sequentially increasing GUID values and hence reduces page contention at the leaf level of the index. However, unlike NEWID() function, the NEWSEQUENTIALID() cannot be used in queries. It can only be used with DEFAULT constraints on table columns of type uniqueidentifier. Consequently, the .Net client that is inserting a a new row has no knowledge of the primary key until the SQL Server has actually created the new row and the NEWSEQUENTIALID() function executed. This poses an issue of how the generated primary key value can be returned to calling .Net application.

Solution:

For .Net client using System.Data.SQLClient

The solution is to use the OUTPUT clause with the insert statement so that the generated primary key can be extracted with the ExecuteScalar() method of the SqlCommand object.

For. Net client using LINQ to SQL

Because the SQL Server generates the primary key in this scenario, the LINQ to SQL will only work correctly if the Column attribute with IsDbGenerated=true property is applied to entity property that corresponds to the underlying primary key column.

Detailed information:

Consider following table definition:

   1: create table Books (Id uniqueidentifier Default NEWSEQUENTIALID() 
   2:                          NOT NULL 
   3:                         CONSTRAINT PK_Books PRIMARY KEY CLUSTERED,
   4:                     Title varchar (32))

.Net client using System.Data.SQLClient

The .Net client application can issue "INSERT INTO Books (Title) Output inserted.Id  VALUES (@Title); " query that specifies the book title. By the virtue of the DEFAULT clause specified for the Id column of the Books table a new GUID will be assigned by the NEWSEQUENTIALID() function. The “Output inserted.Id” clause of the insert statement makes this GUID value available to the .Net client that extract it in “ID = (Guid)cmd.ExecuteScalar();“ statement as shown below:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using System.Data;
   6: using System.Data.SqlClient;
   7:  
   8: namespace Client
   9: {
  10:     class Program
  11:     {
  12:         static public Guid AddBook(string title, string connString)
  13:         {
  14:             Guid ID = Guid.Empty;
  15:             string sql = 
  16:              "INSERT INTO Books (Title) Output inserted.Id  VALUES (@Title); ";
  17:  
  18:             using (SqlConnection conn = new SqlConnection(connString))
  19:             {
  20:                 SqlCommand cmd = new SqlCommand(sql, conn);
  21:                 cmd.Parameters.Add("@Title", SqlDbType.VarChar);
  22:                 cmd.Parameters["@title"].Value = title;
  23:                 try
  24:                 {
  25:                     conn.Open();
  26:                     ID = (Guid)cmd.ExecuteScalar();
  27:                     Console.WriteLine("Added book " + title + " with ID=" + ID.ToString("D"));
  28:                 }
  29:                 catch (Exception ex)
  30:                 {
  31:                     Console.WriteLine(ex.Message);
  32:                 }
  33:             }
  34:             return (Guid)ID;
  35:         }
  36:  
  37:  
  38:         static void Main(string[] args)
  39:         {
  40:             string connString =
  41:                 "Data Source=(local);Initial Catalog=Test;Integrated Security=SSPI";
  42:  
  43:             for (int i = 0; i < 10; i++)
  44:             {
  45:                 string title = "Encyclopedia Volume " + i;
  46:                 Guid id = AddBook(title, connString);
  47:             }
  48:             Console.ReadLine();
  49:         }
  50:     }
  51: }
  52:  

.Net client using LINQ to SQL

The LINQ to SQL will only work correctly in this scenario if you manually add the IsDbGenerated=true attribute for the Id property of the generated Book entity.

Assuming that in Visual Studio 2008 you have added “Linq to SQL Classes” project item and named it Test.dbml and dragged the Books table to the Designer surface, the Test.Designer.cs file will contain generated Entity classes. Open this file and locate the public System.Guid Id property contained in the public partial class Book entity. Apply the IsDbGenerated=true property to the Column attribute of the Id property as shown below:

   1: [Column(Storage="_Id", DbType="UniqueIdentifier NOT NULL", IsPrimaryKey=true, IsDbGenerated=true)]
   2:         public System.Guid Id

Following code fragment demonstrates the insertion of the Linq Book entities

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5:  
   6: namespace LinqClient
   7: {
   8:     class Program
   9:     {
  10:         static public Guid AddBookLinq(string title, TestDataContext db)
  11:         {
  12:             Book bk = new Book();
  13:             bk.Title = "LINQ to SQL";
  14:             db.Books.InsertOnSubmit(bk);
  15:             db.SubmitChanges(System.Data.Linq.ConflictMode.FailOnFirstConflict);
  16:             Console.WriteLine("Linq Added book " + bk.Title + " with ID=" + bk.Id);
  17:             return bk.Id;
  18:         }
  19:  
  20:         static public void PrintBooksLinq(TestDataContext db)
  21:         {
  22:             var books = from b in db.Books
  23:                         select b;
  24:             foreach (var book in books)
  25:             {
  26:                 Console.WriteLine("Book ID=" + book.Id + " Title=" + book.Title);
  27:             }
  28:         }
  29:         static public void DeleteBooksLinq(TestDataContext db)
  30:         {
  31:             var books = from b in db.Books
  32:                         select b;
  33:             db.Books.DeleteAllOnSubmit<Book>(books);
  34:             db.SubmitChanges(System.Data.Linq.ConflictMode.FailOnFirstConflict);
  35:         }
  36:  
  37:         static void Main(string[] args)
  38:         {
  39:             string connString =
  40:                 "Data Source=(local);Initial Catalog=Test;Integrated Security=SSPI";
  41:              TestDataContext db = new TestDataContext(connString);
  42:             
  43:             PrintBooksLinq(db);
  44:             for (int i = 0; i < 5; i++)
  45:             {
  46:                 AddBookLinq("LinqToSql" + i, db);
  47:             }
  48:             PrintBooksLinq(db);
  49:             DeleteBooksLinq(db);
  50:             
  51:             Console.ReadLine();
  52:             DeleteBooksLinq(db);
  53:         }
  54:     }
  55: }

Applies to:

  • .Net Framework 3.5 SP1
  • VS 2008 SP1
  • SQL Server 2005 or SQLServer 2008

References:

“NEWSEQUENTIALID() (Transact-SQL) “

http://msdn.microsoft.com/en-us/library/ms189786.aspx

“Using uniqueidentifier Data”

http://msdn.microsoft.com/en-us/library/ms190215

“OUTPUT Clause (Transact-SQL)”

http://msdn.microsoft.com/en-us/library/ms177564.aspx

Posted by micham | 0 Comments
Filed under: ,

Microsoft Web Platform Installer (Release Candidate)

This Monday we have made available RC of the Microsoft Web Platform Installer (MWPI) - a free tool that makes it simple to download and install the latest components of the Microsoft Web Platform, including Internet Information Services (IIS)  6.0 or 7.0, SQL Server 2008 Express, .NET Framework 3.5 SP1 and Visual Web Developer 2008 Express SP1. The Web Platform Installer offers a single installer to help you obtain the software you need to build and run a complete Web solution on the Microsoft Web platform, whether you are using Windows XP, Windows Server 2003, Windows Vista, or Windows Server 2008.   In addition, the Web Platform Installer checks online to ensure the most current versions and new additions to the Microsoft Web Platform are downloaded.

 

The installer can be downloaded from http://www.microsoft.com/web/channel/products/WebPlatformInstaller.aspx where you can watch a short how to demo that walks you through the installation process. It requires that you have installed .Net Framework 2.0 to start it.

On Microsoft Vista and Windows Server 2008 the installer will offer to install IIS7 and on Windows XP Professional SP3 it will install IIS 5.1. On Windows Server 2003 SP2 it installs IIS 6.0. When started the MWPI checks for new ASP.Net preview technologies, such as ASP.Net MVC Beta or Application Request Routing and offers them to you to install. It also checks for any updates relevant to the components you have already installed. It probably the most convenient way to build a development machine and make sure it is updated with the latest ASP.Net features.

Posted by micham | 0 Comments

ASP.Net 3.5 SP1 Dynamic Data

Dynamic Data (http://msdn.microsoft.com/en-us/library/cc488545.aspx) is one of the new features that has been added in SP1 of .Net Framework 3.5. It allows you to create data-driven web applications with reduced programming effort. It uses an automatically generated object model (using LINQ to SQL object layer) and page and field templates to generate at runtime the web user interface that allows you to manipulate the data. Because of the use of templates the maintenance effort for these web sites should be reduced (you just need to change a page template rather than potentially large number of pages).

You may be interested to review following references that include tutorial videos

• ASP .NET Dynamic Data (Video Links) http://www.myvbprof.com/2007_Version/Dynamic_Data_Tutorial.aspx

• Introduction to ASP.NET Dynamic Data-Dynamic Data in Action Webcasts http://www.asp.net/dynamicdata/

I have delivered the presentation to Spanish ISV Gold Partners on the Partner Learning Center. If you are a Gold Partner you should be able to access the recording at https://training.partner.microsoft.com/plc/details.aspx?publisher=12&delivery=258332 . Below you will find the English version of the presentation that introduces main concepts of this interesting feature.

Posted by micham | 1 Comments

Automating Managed Computer Account Removal in System Center Essentials 2007

One of the partners asked recently about the possibility of automating the removal of managed computer account from System Center Essentials 2007 (SCE). In their scenario the administrator is removing manually the computer account from the Active Directory (also that he verifies that this computer account has been removed from SCE Manged Computers security group ) and would like that the corresponding managed computer account in System Center Essentials be removed automatically.  The idea is to have a scheduled task that executes periodically, queries System Center database for managed computer accounts and checks if this account has been removed from Active Directory. If so it would be removed form SCE too.

It turns out that powershell scripting option is not available with System Center Essentials 2007 ( it is with Operations Manager see http://technet.microsoft.com/en-us/magazine/cc671178.aspx ). However, we can use the SDK API (http://msdn.microsoft.com/en-us/library/bb437506.aspx) and in particular Microsoft.EnterpriseManagement.Administration namespace to automate administrative tasks in System Center. We can obtain a list of SCE managed computer accounts by calling a function ManagementGroupAdministration.GetAgentManagedComputers() which accepts as an argument a filter criteria expression ( see http://msdn.microsoft.com/en-us/library/bb437603.aspx for syntax). This function will return a read only collection of AgentManagedComputer objects representing managed computer accounts. Now that we know how to build a list of SCE accounts it would be tempting to build the list of Active Directory computer accounts storing them in the List<AgentMangagedComputer> structure because we could then use the Except() function (see http://msdn.microsoft.com/en-us/library/bb300779.aspx) of the List to get a list of computer accounts that should be removed. However, it turns out that AgentManagedComputer object does not offer a public constructor. This forces us to perform checks as the SCE managed computer accounts are being enumerated, constructing a list of candidates for deletion.

AgentMangagedComputer.PrincipalName will yield FQDN name of the machine – but what should be the equivalent Active Directory attribute we could query? Well we use csvde command line utility not only to test the syntax of our Active Direcotry query but also to get a list of all the attributes for the queried object. So running following command:

csvde.exe –f out.txt –s localhost –p subtree –r “(objectCategory=Computer)”  we can see that the dnsHostName attribute contains the FQDN name of the machine in Active Directory.

To compile the sample below you will need to add reference to System.ServiceProcess (standard .Net 2.0 assembly) and

  • Microsoft.EnterpriseManagement.OperationsManager.dll
  • Microsoft.EnterpriseManagement.OperationsManager.Common.dll
  • that can be found in the %ProgramFiles%\System Center Operations Manager 2007\SDK Binaries directory of the System Center machine.

       1: using System;
       2: using System.Collections.Generic;
       3: using System.Linq;
       4: using System.Text;
       5: using System.DirectoryServices;
       6: using Microsoft.EnterpriseManagement;
       7: using Microsoft.EnterpriseManagement.Administration;
       8: using Microsoft.EnterpriseManagement.Common;
       9: using System.ServiceProcess;
      10:  
      11: namespace MomSynchTask
      12: {
      13:   
      14:     class ComputerAccount{
      15:         private string _name= "";
      16:         public string Name { 
      17:                 get {return _name;}
      18:                 set{ _name=value;}
      19:         }
      20:         public ComputerAccount(string name){
      21:             Name = name;
      22:         }
      23:         public override string ToString()
      24:         {
      25:             return Name;
      26:         }
      27:     }
      28:  
      29:     class Task
      30:     {
      31:         
      32:         List<ComputerAccount> AdComputerAccounts = new List<ComputerAccount>();
      33:         List<AgentManagedComputer> MomAccountsToDelete = new List<AgentManagedComputer>();
      34:  
      35:         // uses SDK to connect to Sce using current credentials
      36:         protected ManagementGroup ConnectToSce(string hostname)
      37:         {
      38:             Console.WriteLine("Attempting to connect to the SDK Service by using the current user's credentials.");
      39:             ManagementGroup mg = null;
      40:             try
      41:             {
      42:                  mg = ManagementGroup.Connect(hostname);
      43:                 if (mg.IsConnected)
      44:                     Console.WriteLine("Connection succeeded.");
      45:                 else
      46:                     throw new InvalidOperationException("Not connected to an SDK Service.");
      47:             }
      48:             catch (ServerDisconnectedException sde)
      49:             {
      50:                 Console.WriteLine("\nConnection failed. " + sde.Message);
      51:                 if (sde.InnerException != null)
      52:                     Console.WriteLine(sde.InnerException.Message);
      53:  
      54:                 // Call custom method to prompt the user for credentials if needed.
      55:  
      56:             }
      57:             catch (ServiceNotRunningException snr)
      58:             {
      59:                 Console.WriteLine(snr.Message);
      60:  
      61:                 // Make one attempt to start the service.
      62:                 System.ServiceProcess.ServiceController sc = new
      63:                     ServiceController("MOMSDK", hostname);
      64:  
      65:                 Console.WriteLine("Attempting to start the SDK Service on " + hostname + ".");
      66:                 sc.Start();
      67:  
      68:                 // Wait 20 seconds for it to enter the Running state.
      69:                 sc.WaitForStatus(ServiceControllerStatus.Running, new TimeSpan(0, 0, 20));
      70:  
      71:                 if (sc.Status == ServiceControllerStatus.Running)
      72:                 {
      73:                     mg = new ManagementGroup(hostname);
      74:                     Console.WriteLine("Connection succeeded.");
      75:                 }
      76:                 else
      77:                 {
      78:                     throw new InvalidOperationException("Unable to restart and connect to the SDK Service.");
      79:                 }
      80:             }
      81:             return mg;
      82:         }
      83:  
      84:         public void GetAdComputerAccounts()
      85:             {
      86:                 try
      87:                 {
      88:                     DirectoryEntry root = new DirectoryEntry();
      89:                     DirectorySearcher searcher = new DirectorySearcher();
      90:                     searcher.Filter = "(objectCategory=Computer)";
      91:                     searcher.PropertiesToLoad.Add("Name");
      92:                     searcher.PropertiesToLoad.Add("dnsHostName");
      93:                     searcher.PropertiesToLoad.Add("sAMAccountName");
      94:                     searcher.PageSize = 1000;
      95:                     searcher.SearchScope = SearchScope.Subtree;
      96:                     Console.WriteLine("Enumerating computer accounts in Active Directory...");
      97:                     foreach (SearchResult result in searcher.FindAll())
      98:                     {
      99:                         DirectoryEntry entry = result.GetDirectoryEntry();
     100:  
     101:                         ComputerAccount computer = new ComputerAccount((string)entry.Properties["dnsHostName"].Value);
     102:                         AdComputerAccounts.Add(computer);
     103:  
     104:                         Console.WriteLine("Name=" + (string)entry.Properties["Name"].Value + 
     105:                                 " dnsHostName=" + (string)entry.Properties["dnsHostName"].Value +
     106:                                 " sAMAccountName=" + (string)entry.Properties["sAMAccountName"].Value);
     107:                     }
     108:                 }
     109:                 catch (Exception ex)
     110:                 {
     111:                     Console.WriteLine(ex.Message);
     112:                 }
     113:         }
     114:         public ManagementGroupAdministration GetMomComputerAccouts(string hostname)
     115:         {
     116:             ManagementGroupAdministration mga=null;
     117:             ManagementGroup mg = ConnectToSce(hostname);
     118:             if (mg !=null){
     119:                mga = mg.GetAdministration();
     120:               //filter managed computer accounts see http://msdn.microsoft.com/en-us/library/bb437603.aspx
     121:               AgentManagedComputerCriteria criteria = new AgentManagedComputerCriteria("Name LIKE '%'");
     122:               foreach (AgentManagedComputer momAccount in mga.GetAgentManagedComputers(criteria))
     123:               {
     124:                 //BIOS name of computer
     125:                 Console.WriteLine("BIOS Name=" + momAccount.ComputerName + " FQDN Name=" + momAccount.PrincipalName + " Name=" + momAccount.Name);                
     126:                 //check if this computer Exists in Active Directory- we compate  PrincipalName with dnsHostName 
     127:                 if (AdComputerAccounts.Exists(p => p.Name.Equals(momAccount.PrincipalName, System.StringComparison.InvariantCultureIgnoreCase)) == false)
     128:                 {
     129:                     Console.WriteLine("Added Mom Computer Account:" + momAccount.PrincipalName + " to deletion list");
     130:                     MomAccountsToDelete.Add(momAccount);
     131:                 }
     132:                 else
     133:                 {
     134:                     Console.WriteLine("Computer Account:" + momAccount.PrincipalName + " appears to exist in Active Directory.");
     135:  
     136:                 }
     137:  
     138:               }
     139:             }
     140:             return mga;
     141:         }
     142:         public void RemoveMomComputerAccounts(string[] args, ManagementGroupAdministration mga)
     143:         {
     144:  
     145:          mga.DeleteAgentManagedComputers(MomAccountsToDelete);
     146:          Console.WriteLine("Removed Mom Computer accounts");
     147:          return;
     148:         }
     149:     }
     150:  
     151:     class Program
     152:     {
     153:         static void Main(string[] args)
     154:         {
     155:             Task t = new Task();
     156:             //Get list of computer accounts from Active Directory
     157:             t.GetAdComputerAccounts();
     158:             //Get list of Ops Manager managed computer accounts to be deleted
     159:             ManagementGroupAdministration mga = t.GetMomComputerAccouts("localhost");
     160:             //Remove managed computer accounts that have no corresponding Active Directory account
     161:             // if /d switch has been specified on command line
     162:             if (args.Length > 0)
     163:             {
     164:                 if ((mga != null) && (args[0].Equals("/d")))
     165:                     t.RemoveMomComputerAccounts(args, mga);
     166:             }
     167:         }
     168:         
     169:     }
     170: }
    Posted by micham | 1 Comments

    Getting Mailbox Information from Exchange 2003 Cluster

    I was recently tinkering with Visual Basic Script that would obtain the user mailbox information from the Exchange 2003 cluster. In particular I was interested in getting mailbox information stored in Active Directory as well as obtaining current mailbox status (such as current size of the mailbox).

    As my VB script skills are dated the first thing was to gather basic resources such as:

    Querying Cluster Information

    In general the cluster name may or may not be associated with the node that has Exchange resources so we cannot send the query to the cluster name. Also it is common to see Active-Active-Pasive Exchange clusters where the mailboxes are partitioned among the active nodes. To ensure we get complete list of all mailboxes we should query all active nodes with Exchange resources.

    Armed with the Scriptomatic we quickly see that we can make a WMI query on the MSCluster WMI class to obtain the list of all nodes of the (Windows) cluster. We can then check the status of these nodes and return in the array the list of active nodes. Here we use the semi-synchronous call that works faster especially for large result sets (we will appreciate it later when we query for mailbox status). Essentially we can start enumerating objects before the entire result set is available-you can find further detail in

    “Making a Semisynchronous Call with VBScript”

    http://msdn2.microsoft.com/en-us/library/aa392301(VS.85).aspx

    In the WMI query we are getting all the cluster nodes nodes and we are specifying "WQL" as the type of the query language (I think the only one available still with WMI) so that we can get to specify flags required for semi-synchronous call.

    'Checks for cluster configuration if available. In non-clustered information will return empty array
    ' WMI query is issued against the cluster name to return the list of the nodes of the cluster. We then
    ' add to dymanic array nodes that are active

    Function GetClusterNodes(cluster)
      Dim activeNodes()
      ReDim activeNodes (0)

      Const wbemFlagReturnImmediately = &h10
      Const wbemFlagForwardOnly = &h20

      'On Error Resume Next
      WScript.Echo "Getting cluster configuration from " & cluster
      Set objWMIService = GetObject("winmgmts:" _
            & "{impersonationLevel=impersonate}!\\" & cluster & _
                "\root\mscluster")
      Set colItems = objWMIService.ExecQuery ("Select * from MSCluster_Node", "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
      For Each objItem in colItems
        If CInt(objItem.State)=0 Then     
          activeNodes(UBound(activeNodes))=objItem.Name
          ReDim Preserve activeNodes(UBound(activeNodes)+1)
        End If
        WScript.Echo "Name: " & objItem.Name
        WScript.Echo "State: " & objItem.State
        WScript.Echo "Status: " & objItem.Status
        WScript.Echo "Roles: " & objItem.Roles
        WScript.Echo "-----------------------------------------------------"
      Next

      ReDim Preserve activeNodes(UBound(activeNodes)-1)
      WScript.Echo "Active Nodes are:"
      For Each node in activeNodes
        WScript.Echo "Node=" & node
      Next
      Set objWMIService = Nothing
      GetClusterNodes = activeNodes
    End Function

    The alternative to this approach would be to query for NodeToActiveGroup class.The advantage is that it returns the active nodes of the cluster and that it allows us to check the cluster Resource Groups so we could try to get Active nodes with Exchange resources. However, because strings are returned parsing is more complex. Another issue is that in Cluster Administrator tool the users can change the name of the Resource Group so it is problematic to do a test on the PartComponent property for Exchange resource groups. The code below does not to it and prevents adding the same node twice (once for OS resource group and second time for Exchange resource group).

    ' Query the cluster for the list of active nodes. Note we parse out the name of the node from
    ' the GroupComponent property. We test is node has not been added already as same node appears
    ' multiple times (for each cluster resource). As nodes are added we
    '  we resize the activeNodes array dymamically.

    Function GetActiveClusterNodes (cluster)
    'On Error Resume Next
      Const wbemFlagReturnImmediately = &h10
      Const wbemFlagForwardOnly = &h20
      WScript.Echo "Querying cluster "  & cluster & " for active Exchange nodes..."

      Set objWMIService = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\" & cluster & "\root\MSCluster")
      Set colItems = objWMIService.ExecQuery("SELECT * FROM MSCluster_NodeToActiveGroup", "WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)

      Dim activeNodes()
      ReDim activeNodes (0)
      label="MSCluster_Node.Name="
      For Each objectitem In colItems
        groupComponent = Trim(objectitem.GroupComponent)
        WScript.Echo "Group=" & objectitem.GroupComponent & " Part="  & objectitem.PartComponent
        If InStr(groupComponent,label)>0 Then
          node = Mid ( groupComponent, Len(label)+2, Len(groupComponent)-Len(label)-2 )
          found = False
          For Each n in activeNodes
             If StrComp( n, node)=0 Then
           found=True
               Exit For
             End If
          Next
          If found = False Then
            activeNodes(UBound(activeNodes)) = node
            ReDim Preserve activeNodes(UBound(activeNodes)+1)
          End If
        End If
      Next
      ReDim Preserve activeNodes(UBound(activeNodes)-1)

      WScript.Echo "Active Nodes detected:"
      For Each node in activeNodes
        WScript.Echo node
      Next

      Set objWMIService = Nothing
      GetActiveClusterNodes = activeNodes
    End Function

    With all this it seems that first version is better because we cannot guarantee that the Administrator will not renaming resources in Cluster Administrator tool. We simply will then query all the active nodes, and for the active nodes with no Exchange resources we will simply not see any mailboxes returned. Assuming CLUSTER_NAME holds the name of the cluster we iterate as follows

    Dim EXCHANGE_SERVER_NODES
    EXCHANGE_SERVER_NODES = GetClusterNodes(CLUSTER_NAME)

    For Each server In EXCHANGE_SERVER_NODES
      WScript.Echo "Querying mailbox status on node " & server
      GetMailboxStatus(server)
      WScript.Echo Now()
    Next

    Getting Mailbox Status information

    So now we can put together a function to return us the mailboxes for a given machine name. In this case we skip all the system mailboxes as we are interested just in "ordinary" users. Unlike with ADSI there is no easy way of doing the filtering in the WQL so we have to filter in code in this case.

    'Uses WMI interface to query the Exchange node 'server' for mailbox status information.
    'Note that the query will return ALL mailboxes (included system mailboxes and mailboxes to be
    'deleted or reassigned to a different user.
    'The subroutine will ignore all system mailboxes

    Sub GetMailboxStatus(server)
      Const wbemFlagReturnImmediately = &h10
      Const wbemFlagForwardOnly = &h20

      Set objWMIService = GetObject("winmgmts:" _
            & "{impersonationLevel=impersonate}!\\" & server & _
                "\ROOT\MicrosoftExchangeV2")
      Set colItems = objWMIService.ExecQuery ("Select * from Exchange_Mailbox","WQL", wbemFlagReturnImmediately + wbemFlagForwardOnly)
      k=0
      total=0   
      For Each objItem in colItems
        total=total+1
        'Exclude System Mailboxes
        If InStr(UCase(objItem.LegacyDN), "CN=SYSTEMMAILBOX") = 0 And InStr(UCase(objItem.LegacyDN), "CN=SMTP") = 0 _
         And InStr(UCase(objItem.LegacyDN), "CN=MICROSOFT SYSTEM ATTENDANT")=0 Then      
           k=k+1
          WScript.Echo k & " MailboxName=" & objItem.MailboxDisplayName & " StorageLimitInfo="& objItem.StorageLimitInfo
          WScript.Echo  " Server Name=" & objItem.ServerName & " StorageGroup=" & objItem.StorageGroupName & " Store Name=" & objItem.StoreName 
        End If
    Next
    WScript.Echo " Detected " & k & " user mailboxes (total="& total &"). Skipped " & total - k & " system mailboxes."
    Set objWMIService = Nothing
    End Sub

    Querying Mailbox information in Active Directory using ADSI

    Now that we have the MailBox status information we turn to querying for user mailbox information stored in Active Directory. The default queries are limited to results sets of just 1000 object. In order to process large result sets we have to implement a paged query. This is achieved by specifying "Page Size" property on the Command object. Now the server will return to client data in chunks having at most Page Size objects. The paging mechanism is transparent to the client so no special code to handle paging is required.

    “Retrieving Large Results Sets” http://msdn2.microsoft.com/en-us/library/aa746459(VS.85).aspx has more information about this topic. It is also recommended not to cache the results so we set the "Cache Results" property to False.

    Our query uses a filter to return the user mailboxes skipping any System Mailbox as we do not want to process them in this case.

    'Queries Active Directory for mailbox configuration. The query will list only user mailboxes, skipping system mailboxes.

    Sub GetMailBoxes()

    Const DOMAIN_CONTROLLER = "NoddyDCGC"
    Const DOMAIN_LDAP = "DC=noddy,DC=com"

    strADsPath = "LDAP://" & DOMAIN_CONTROLLER & "/" & DOMAIN_LDAP

    'Open connection to AD
    Set objConnection = CreateObject("ADODB.Connection")
    objConnection.Open "Provider=ADsDSOObject;"


    Set objCommand = CreateObject("ADODB.Command")
    objCommand.ActiveConnection = objConnection
    objCommand.Properties("Page Size") = 500
    objCommand.Properties("Timeout") = 10     ' Seconds
    objCommand.Properties("Cache Results") = False 


      'query for user object in Active Directory
      objCommand.CommandText = "<" & strADsPath  & ">" & ";(&(objectClass=user)(homeMDB=*)(!CN=SystemMailbox{*}))" & ";distinguishedName,name" & ";subtree"

      'Execute search to get Recordset
      Set objMailboxRS = objCommand.Execute 

      total=0
      While Not objMailboxRS.EOF            
          'Bind to mailbox object for the current user   
          Set objMailbox = GetObject("LDAP://" & DOMAIN_CONTROLLER &"/" & objMailboxRS.Fields("distinguishedName") )
          Set objStore   = GetObject("LDAP://" & DOMAIN_CONTROLLER &"/" & objMailbox.homeMDB)
          'Update mailbox information in Database                   
          WScript.Echo "Mailbox " & objMailbox.name & " is stored in " & objStore.cn
          total=total+1             
          objMailboxRS.MoveNext
        Wend   'End While EOF
      WScript.Echo "Detected " & total & " user mailboxes."   
      objMailboxRS = null                                      
    End Sub                       

    Differences between mailbox numbers returned by WMI and ADSI

    In general, the number of mailboxes returned by querying Active Directory using ADSI interface will be different from that reported by WMI query. This discrepancy can occur for example if a user is deleted from Active Directory but the associated mailbox has not yet been purged (or reassigned to a different user). Also the opposite may occur where a user account has already been created but the mailbox has not yet been created (because he never accessed it nor received yet and email).

    File Properties in Windows Vista

    When you copy files from Windows XP or Windows Server 2003 operating systems to Windows Vista you will see that the properties are arranged differently and some are missing. In particular the file properties gathered in the "Summary" tab have missing values. If you programmatically manipulated the properties through the Shell32 API it will work fine in XP but will not work in Vista.

    Vista File Properties

    Figure 1. File Properties for the same physical file show in the Windows XP and Windows Vista Shell.

    The explanation is that in Windows Vista the support for secondary NTFS stream properties has been dropped from the Shell. This is because if the file is send via e-mail or copied to non- NTFS file system (a lot of pen drives are formatted as FAT) these properties are lost anyway. So in Vista the file properties have been reengineered to be stored with the physical file and require a property handler. Vista has a number of built in property handlers for certain formats, for example OLEDoc File that you can register for a given file extension but only if the file has been stored in this format.

    But is there any alternative to Shell32 API to get your program working in Vista if it needs to manipulate the secondary NTFS stream properties? After all Vista uses NTFS file system and the file properties are still there (you can make sure by creating a network share on Vista and viewing the file with XP client machine. Following document describes the alternative and has source code sample for accessing the properties

    "The Dsofile.dll files lets you edit Office document properties when you do not have Office installed"

    http://support.microsoft.com/?kbid=224351

    Of course if you are migrating your application to Vista, it is recommendable to remove the dependency of your application on these properties but there may be scenarios where this is not possible and where the above article provides you with a workaround.

    Posted by micham | 2 Comments
    Filed under:

    September SharePoint Resource Collection

    The September SharePoint Resource collection is a recompilation of interesting resources such as whitepapers, blogs or webcasts on SharePoint technology.

    Posted by micham | 1 Comments
    Filed under:

    Attachment(s): SharePoint_Resources_Sep_2007.pdf

    Microsoft Business Data Catalog Definition Editor

    Summary:

    The Microsoft Business Data Catalog Definition Editor is available in the recent release of the MOSS2007 SDK 1.2

    Overview:

    The Business Data Catalog Definition Editor provides a visual tool for creating an Application Definition for the BDC in MOSS 2007.  Features include:

    • Underlying XML is abstracted by the design surface and properties window
    • Drag and drop web methods, tables, or views to create line of business (LOB) connections.
    • Entities and methods are created automatically from database metadata and WSDLs.
    • Additional method instances can be added to further enhance the DB or WS connection.
    • Method instances can be tested from within the tool, enabling incremental development of LOB connections

    BDC Editor

    Background:

    Currently, writing an application definition to connect the BDC to a LOB system is a manual process.  This requires an understanding of both how the LOB system is configured and what must be included in the XML to satisfy the BDC. Having a tool to simplify this process not only lowers the initial knowledge threshold for administering the BDC, it also lessens the required work of the user (such as testing, making modifications, etc.). 

    The tool has been designed to assist in the lifecycle management for Application Definition files.  The tool enables searching over databases and web service-based repositories, as well as the connection for BDC web parts in MOSS. 

    Highlights:

    • Tool supports Databases (SQL, Oracle, OLEDB, and ODBC) and Web Services
    • Drag and drop design surface for selecting DB tables or web methods
    • Metadata is automatically extracted from Databases by dragging and dropping tables
    • Web Services require a few additional steps to completely configure the connection
    • Users can import and export Application Definition XML files
    • Users are able to test method instances incrementally from within the tool
    • The tool is not required to run on a Web Front-End
    • Associations are created automatically when foreign keys are selected; they can also be created easily for web services by adding an Association method instance
    Posted by micham | 1 Comments
    Filed under:

    Windows SharePoint Services SDK 1.2 and MOSS 2007 SDK 1.2 have been released 21 August

    ·         MOSS 2007 SDK 1.2.  Includes Conceptual and Class Library Reference documentation, Web Services documentation, and Developer Tools and Samples for MOSS and WSS.  See below for a detailed breakdown what’s new in this release.

    http://www.microsoft.com/downloads/details.aspx?FamilyId=6D94E307-67D9-41AC-B2D6-0074D6286FA9&displaylang=en

     

    ·         WSS 3.0 SDK 1.2.  Includes Conceptual and Class Library Reference documentation, Web Services documentation, and Developer Tools and Samples for WSS technology only.  See below for a detailed breakdown what’s new in this release.

    http://www.microsoft.com/downloads/details.aspx?familyid=05E0DD12-8394-402B-8936-A07FE8AFAFFD&displaylang=en

    New Tools Included with the MOSS SDK

    We rounded out our tool set in this release to include developer tools and samples for the following areas of MOSS development.  New tools and samples are in bold.

    ·         Business Data Catalog Samples and Utilities

    §  Microsoft Business Data Catalog Definition Editor

    §  Sample Pluggable SSO Provider

    §  WSHelloWorld Web Service

    §  WSOrders Web Service

    §  Excel Services User Defined Function Sample

    §  WSOrders Custom Proxy Sample

    §  Amazon Web Service Sample

    §  AdventureWorks Metadata Samples

    §  SAP Sample

    ·         Document Management and Content Processing Samples

    §  Comment Scrub Document Converter

    §  Term Replacement Document Inspector

    ·         Search Samples

    §  Sample Protocol Handler

    §  Custom Content Source

    ·         Records Management and Policy Samples

    §  De-Duplication Router

    §  Document Integrity Verifier

    §  Records Center Web Service Console Application

    §  Search, Collect, and Hold Tool

    §  Sample Custom Barcode Generator

    §  IRM Document Protector

    ·         Workflow Samples

    §  Custom Workflow Report Query Generator

    §  Custom Workflow Report XLSX Injector

    §  Visual Studio Workflow Templates

    §  Enterprise Content Management Workflow Activities

    §  List Item Activities

    §  Hello World Sequential Workflow

    §  State Based Approval Workflow

    §  Modification Workflow

    §  Replication and Contact Selector Workflow

    §  Intersystem Purchase Order

    §  Confidential Approval Workflow

    §  Group Approval Workflow

    §  Approval Workflow Sample

    §  Multi-Stage Workflow

    §  Server-side Collect Signatures Workflow

    Full details can be found in the Welcome Guide of the SDK, accessible through the Start Menu.

    You can browse the SDK online through the following links:

    ·           Office SharePoint Server 2007 SDK

    ·           Windows SharePoint Services 3.0 SDK

    Posted by micham | (Comments Off)
    Filed under:
     
    Page view tracker