PROGRAMMATICALLY Change service account’s password for Windows Services, iis app pools and scheduled tasks – C#

Nowadays it’s becoming a very common practice to frequently (half-yearly or quarterly) change password for all AD Service Accounts. It’s done as part of password compliance security guidelines, specially post GDPR implementations.

Earlier for BizTalk environments we use to have “Never Expire Password” for service account and used it to configure BizTalk Services (like Host Instances, ENTSSO & RuleEngineUpdateService), IIS App Pools or SQL Server Services. We also used BizTalk Service Account to configure multiple custom utilities for maintenance purpose as Windows Scheduled Tasks or as Windows Service.

Changing password manually for all these services, scheduled tasks and App Pools is very cumbersome and risky task. If we fail miss or mistype password even at one place, it may cause account lock issue and bring complete environment at halt.

Note – This problem can be addressed in BizTalk 2020 by using Group Managed Service Accounts as they don’t require Passwords.

Refer – What’s New in BizTalk Server 2020

Group Managed Service AccountsExtend windows GMSA support to BizTalk operations and services.

Using Group Managed Service Account

To address this issue in, I have created Windows Application (works for all the version of BizTalk)

It takes three inputs –

  1. Servers (Comma Separated) => List of Servers where change is required. Ex – In a multi-server BizTalk Server, provide name of all the BizTalk App Servers
  2. Service Account => AD Account for which password needs to be changed
  3. New Password

Along with a Checkbox to select features which require change –

  1. Windows Services – Includes BizTalk Host Instances, ENTSSO, RuleEngineUpdateService, IIS and other custom services using this account
  2. IIS App Pools
  3. Scheduled Tasks – Configured with custom utilities

Along with a Checkbox to select features which require change –

  1. Windows Services– Includes BizTalk Host Instances, ENTSSO, RuleEngineUpdateService, IIS and other custom services using this account
  2. IIS App Pools
  3. Scheduled Tasks – Configured with custom utilities

Allowed Operations –

  1. Stop All Services – It acts as a “Pre-requisites/Prepare for change” step to bring down BizTalk Environment. It will STOP all the Host Instances – irrespective of service account, ENTSSO, RuleEngineUpdateService and IIS.
  2. Get List – It retrieves a list of all the services, app pools and scheduled task which are configured with given service account.
  3. Change Password – It changes password for all the services, app pools and scheduled task configured with given service account.
  4. START all Services – It acts as post change activity to bring BizTalk online by starting all the Host Instances, ENTSSO, RuleEngineUpdateService and IIS

You can choose to perform password change activity on all or any – Services, App Pools and Windows Scheduled Tasks.

Code Explanation –

  1. Windows Services – Getting and Updating password for the list of services configured for given service account.

I have used WMI Queries –

SELECT * FROM Win32_Service where StartName like '%<Service Account Name>%'

Sample Code to get the list –

string strWMINamespace = @"\\" + strServer + @"\root\cimv2";
                if (strServiceAccount.Contains('\\'))
                {
                    //If service account contains backslash it needs to be replaced by double backslash
                    strServiceAccount = strServiceAccount.Replace(@"\", @"\\");
                }
                //SELECT * FROM Win32_Service where StartName like '%<Service Account Name>%'
                //Yields BizTalk Host Instances and ENTSSO
                string strWMIQuery = @"SELECT * FROM Win32_Service where StartName like '%" + strServiceAccount + "%'";
                //Create EnumerationOptions and run wql query  
                EnumerationOptions enumOptions = new EnumerationOptions();
                enumOptions.ReturnImmediately = false;
                using (ManagementObjectSearcher searchObject = new ManagementObjectSearcher(strWMINamespace, strWMIQuery, enumOptions))
                {
                    //Enumerate through the result set and stop if not stopped and Change Password  
                    foreach (ManagementObject service in searchObject.Get())
                    {
                     //Iterate over list of services and perform operation
                     //Updating the Password, refer - https://morgantechspace.com/2015/03/csharp-change-service-account-username-and-password.html#wmi
                     //Creating account object
                     object[] accountParams = new object[11];
                     //Updating Username for testing
                     //accountParams[6] = testUserName;
                     accountParams[7] = strNewPassword;
                     uint returnCode = (uint)service.InvokeMethod("Change", accountParams);
                     if (returnCode == 0)
                       {
                         updateLogs("Password changed successfully for service - " + service["Name"] + "");
                       }
                    }
                }

2. IIS App Pools => Getting and updating password for list of App Pools configured using given service account.

 using (ServerManager IISServerManager = ServerManager.OpenRemote(strServer))
 {
   foreach (ApplicationPool appPool in IISServerManager.ApplicationPools)
    {
      if (appPool.ProcessModel.UserName.ToLower().Contains(strServiceAccount))
      {
         updateLogs("Attempting to change password for App Pool - " + appPool.Name);
         appPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
         //Updating Username for testing
         //appPool.ProcessModel.UserName = testUserName;
         appPool.ProcessModel.Password = strNewPassword;
         boolRequiresCommit = true;
       }
    } 
    if (boolRequiresCommit)
     {
         IISServerManager.CommitChanges();
         updateLogs("Password changed successfully for App Pools");
     }
 }

3. Windows Scheduled Tasks => Getting and updating password for list of Windows Scheduled Tasks configured using given service account.

Microsoft.Win32.TaskScheduler.TaskService taskService = new TaskService(strServer);
foreach (Microsoft.Win32.TaskScheduler.Task task 
in taskService.RootFolder.GetTasks().Where
                    (t => t.Definition.Principal.UserId.ToString().ToLower().Contains
                          (strServiceAccount.ToLower())))
{
     //Disabling each task if it's enabled
     if (task.State != TaskState.Disabled)
       {
         task.Stop();
         task.Enabled = false;
       }
string strRunAsCMD = @"/C schtasks.exe /CHANGE /S " + strServer + " /TN \"" + task.Name + "\" /RU " + strServiceAccount + " /RP " + strNewPassword + "";
    updateLogs("Attempting to change password for task - " + task.Name + "");
    executeCommand(strRunAsCMD);
    updateLogs("Password changed successfully for task - " + task.Name + "");
}
 
//Code to Execute command in Windows Command Prompt                
private void executeCommand(string strRunAsCMD)
{
    var processInfo = new ProcessStartInfo("cmd.exe", strRunAsCMD);
    processInfo.CreateNoWindow = true;
    processInfo.UseShellExecute = false;
    processInfo.RedirectStandardError = true;
    processInfo.RedirectStandardOutput = true;
    processInfo.Verb = "runas";

    var process = Process.Start(processInfo);

    string strProcessOutput = process.StandardOutput.ReadToEnd();
    string strProcessError = process.StandardError.ReadToEnd();

    string logMsg = "executeCommand - Command Execution Completed with Exit Code : " +               process.ExitCode.ToString() + Environment.NewLine;
   if (String.IsNullOrEmpty(strProcessError))
    {
      logMsg += "Process Output - " + strProcessOutput;
    }
    else
    {
       logMsg += "Process Error - " + strProcessError;
    }
    updateLogs(logMsg);

    process.WaitForExit();
    //Checking if the process execution failed
    if (process.ExitCode != 0)
    {
       updateLogs("executeCommand - Error occurred in execution." + Environment.NewLine + "Exit Code - " + process.ExitCode + Environment.NewLine +
                           "Process Error Output - " + strProcessError);
     }

 process.Close();
}

4. Starting/Stopping Host Instances

private async System.Threading.Tasks.Task stopOrStartAllHostInstances(HostAction hostAction)
        {
            try
            {
                //Create EnumerationOptions and run wql query  
                System.Management.EnumerationOptions enumOptions = new System.Management.EnumerationOptions();
                enumOptions.ReturnImmediately = false;

                //Search for all HostInstances of 'InProcess' type in the Biztalk namespace scope  
                System.Management.ManagementObjectSearcher searchObject =
                    new System.Management.ManagementObjectSearcher("root\\MicrosoftBizTalkServer",
                    "Select * from MSBTS_HostInstance Where HostType=1 And IsDisabled=False", enumOptions);

                //Enumerate through the result set and stop each HostInstance if it is running  
                foreach (ManagementObject inst in searchObject.Get())
                {
                    Task<string> task = new Task<string>(() => stopOrStartHostInstance(inst, hostAction));
                    task.Start();
                    updateLogs("Attempting to perform " + hostAction + " on HostInstance of Host: " + inst["HostName"] + " on Server: " + inst["RunningServer"] + "." + Environment.NewLine);
                    string status = await task;
                    updateLogs("Successfully performed " + hostAction + " action on " + inst["HostName"] + " on Server: " + inst["RunningServer"] + "." + Environment.NewLine + Environment.NewLine);
                }
            }
            catch (Exception excep)
            {
                string strExceptionMessage = "Exception occurred in function-stopOrStartAllHostInstances. Message - " + excep.Message + Environment.NewLine + "StackTrace - " + excep.StackTrace;
                throw new Exception(strExceptionMessage);
            }
        }

private string stopOrStartHostInstance(ManagementObject inst, HostAction hostAction)
        {
            try
            {
                if (hostAction == HostAction.STOP)
                {
                    //Check if ServiceState is 'Started'  
                    if (inst["ServiceState"].ToString() == "4")
                    { inst.InvokeMethod("Stop", null); }
                }
                if (hostAction == HostAction.START)
                {
                    if (inst["ServiceState"].ToString() == "1")//Status Stopped
                    {
                        inst.InvokeMethod("Start", null);
                    }
                }
                return "Success";
            }
            catch (Exception excep)
            {
                string strExceptionMessage = "Exception occurred in Function - stopOrStartHostInstance. Message - " + excep.Message + Environment.NewLine + "StackTrace - " + excep.StackTrace;
                throw new Exception(strExceptionMessage);
            }
        }

5. Starting/Stopping Services

private async System.Threading.Tasks.Task stopORStartAdditionalServices(string strServerNames, ServiceAction serviceAction)
        {
            try
            {
                foreach (string strServerName in strServerNames.Split(','))
                {
                    ServiceController[] scServices = ServiceController.GetServices(strServerName);
                    foreach (ServiceController service in scServices)
                    {
                        //Stopping/Starting IIS, RULEEngineUpdateService and ENTSSO services
                        if (service.ServiceName.ToUpper().Contains("W3SVC") || service.ServiceName.ToUpper().Contains("RULEENGINEUPDATESERVICE") || service.ServiceName.ToUpper().Contains("ENTSSO"))
                        {
                            Task<string> task = new Task<string>(() => stopOrStartService(service, serviceAction));
                            task.Start();
                            updateLogs("Attempting to " + serviceAction + " - " + service.DisplayName + " On Server - " + strServerName + Environment.NewLine);
                            string result = await task;
                            updateLogs("Successfully performed Action - " + serviceAction + " on Service - " + service.DisplayName + " On Server - " + strServerName + Environment.NewLine);
                        }
                    }
                }
            }
            catch (Exception excep)
            {
                string strException = "Exception Occurred in function - stopORStartAdditionalServices. Message - " + excep.Message + Environment.NewLine + "Stack Trace - " + excep.StackTrace + Environment.NewLine;
                throw new Exception(strException);
            }
        }

private string stopOrStartService(ServiceController service, ServiceAction serviceAction)
        {
            try
            {
                if ((serviceAction == ServiceAction.STOP) && (service.Status != ServiceControllerStatus.Stopped))
                {
                    //stopping the service 
                    service.Stop();
                }
                else if ((serviceAction == ServiceAction.START) && (service.Status == ServiceControllerStatus.Stopped))
                {
                    //Starting the services
                    service.Start();
                }
                return "Success";
            }
            catch (Exception excep)
            {
                string strException = "Exception Occurred in function - stopOrStartService. Message - " + excep.Message + Environment.NewLine + "Stack Trace - " + excep.StackTrace + Environment.NewLine;
                throw new Exception(strException);
            }
        }

Note – Post this password change activity we started getting error for BAM Portal

Configuration Error –

Description: An error occurred during the processing of a configuration file required to service this request. Please review the specific error details below and modify your configuration file appropriately.

Parser Error Message: Could not create Windows user token from the credentials specified in the config file. Error from the operating system ‘The user name or password is incorrect.’

This error is bound to come and is explained in detail in below article –

https://prashantbiztalkblogs.wordpress.com/2020/07/14/bam-portal-error-could-not-create-windows-user-token-from-the-credentials-specified-in-the-config-file-error-from-the-operating-system-the-user-name-or-password-is-incorrect/

Hope it’s helpful.

Download the complete code from here

Contact Me:- 

@Gmail@Facebook , @Twitter, @LinkedIn @MSDNTechnet, @My Personal Blog 

Contact Me –

Check out my other blogs –

Logic Apps Secure SQL Connection String in Azure Key Vault

While using SQL Connector in Logic Apps we don’t get a straightforward way to fetch/store connection string in Key Vault or in config. By default, the connection string we provide goes and sits in the Configurations as shown in the pics below and is in plaintext format. The regular process to use SQL Connector -…

Save Full File in SQL table Column (varbinary) and read the contents back in t

What if you needed to save full file as-is to a SQL Server table column. I ran into a similar scenario and had to save CSV Files in a table. For this we created a table with a column of type “varbinary” CREATE TABLE [dbo].[TableWithCompleteFiles]([SNo] [int] IDENTITY(1,1) NOT NULL,[FileName] varchar NULL,[FileContent] varbinary NULL) ON [PRIMARY]…

Export SQL Table content as CSV File to a File Location

Recently, I got a request to save the contents of the table to a File. Wroking Command – Above commands will fail with below error – Msg 15281, Level 16, State 1, Procedure xp_cmdshell, Line 1 [Batch Start Line 43]SQL Server blocked access to procedure ‘sys.xp_cmdshell’ of component ‘xp_cmdshell’ because this component is turned off…

Advertisement