Tuesday, January 23, 2018

SCCM deploy application

SCCM

We are working upgrade Fire Truck dispatch client software. Here is the situation:

·         Client machine is Toughbook with LET on the road and Wifi in Fire station
·         Client is in locked down desktop and account is not in local administrators group
·         Upgrade schedule is depends on the each truck. They can do the upgrade if they are not in the call
·         Upgrade package will including uninstall old version and install new version. The package size is around 500M


Solution:

Because the connective and install schedule, we decided to use SCCM with two application package. The first application will copy all the files to the Toughbook and second application will run the uninstall/install software.

Problem, there is always problems :(


Here is some concept of SCCM application deployment we found:


User Experience page of the Create Deployment Type Wizard,
Install for user:
Application will be installed by using logged on user account for the software installation
Install for System:
Application will be installed by using system account

 Deployment Settings page of the Deploy Software Wizard
Available
If the application is deployed to a device, the user will see it in the Software Center and can install it on demand.
Required
The application is deployed automatically according to the configured schedule. However, a user can track the application deployment status if it is not hidden, and can install the application before the deadline by using the Software Center.


Limitation of Software Canter:
The Software Center shows everytime "No items found
Another user was logged in. It seems that the Software Center will only display packages for the first/default logged on user.
Make sure the previous user logs off completely (not just disconnected) and there are no users logged on to the machine before another user logs on.
https://social.technet.microsoft.com/Forums/en-US/86ef46a6-b0f4-424e-b71d-7906b6255d3e/sccm-2012-client-software-center-no-items-found?forum=configmanagergeneral


Application: copy job

·         For the first application, we pre-deploy the application 3 days in advance and set the application as Install for System and as Required
·         Because the robocopy cannot return the code 0, it always shows "Error" event the copy job is success. Add exist code 0 to the batch file. Below is the batch file:
robocopy .\ c:\tfs\Files\MPS91_to_93_Upgrade\ /e
set ERRORLEVEL=0
Exit /b 0

Application: install Job

·         For the first application, we pre-deploy the application 3 days in advance and set the application as Install for System and as Available. so user will decide when to do the installation
·         We found that when run the application by system account, it requires a reboot after uninstall old application. It is always failed at first try and success after reboot and re-try. It does not happen when runs under local administrator user account. However, user is not local administrator and they do not know the password :(
·         We are thinking use Runas, but Runas cannot put the password into the command line :(
·         So we write a c# windows app, but an account into local admin group and call the patch file run the installation. Below is the c# program:
RUNASDMZrun.exe
static void Main(string[] args)
        {
           
            if (args.Length < 1)
            {
                Console.WriteLine("Please input running program");
                return;
            }
           
                string command = args[0];
                Console.WriteLine(command);
            string username = "SCCMInstall";
           
          var pcLocal = new PrincipalContext(ContextType.Machine);
          var group = GroupPrincipal.FindByIdentity(pcLocal, "Administrators");

          var pcDomain = new PrincipalContext(ContextType.Domain, "XXXXX");
          try
          {
              group.Members.Add(pcDomain, IdentityType.SamAccountName, username);
              group.Save();
          }
          catch {
         
          }
           
            var psi = new ProcessStartInfo();
            SecureString ss = new SecureString() ;
            //construct the password string 
            string password="uJ%$iOY%$sdE6lt";
            var charArrayList = password.ToCharArray();
            for (int i = 0; i < charArrayList.Length; i++)
            {

                ss.AppendChar(charArrayList[i]);
            }
           
            psi.Domain = "XXXXX";
            psi.UserName = username;
            psi.Password = ss;
            psi.FileName = command;
            psi.UseShellExecute = false;

            var process = Process.Start(psi);
            process.WaitForExit();
            try
            {
                group.Members.Remove(pcDomain, IdentityType.SamAccountName, username);
                group.Save();
            }
            catch {
            }

        }

Here is the batch file in the SCCM application

cd c:\tfs\Files\MPS91_to_93_Upgrade\
RUNASDMZrun.exe "91upgratetol93.bat"
set ERRORLEVEL=0
Exit /b 0

·         I know, it is not security since we put the password in the Exe. The domain account SCCMInstall, is temporary account, account will be disable and change the password after the deployment.



Friday, May 17, 2013

Powershell to EXE : Please do not touch my code :)

We did a lots of Powershell to generate report or automatic task. It is powerful but it is less security. We found someone touched and changed our script because it is in clear txt file. so we developed some thing to encrypt the PS1 script.

Here is what the script can do:

we have a hello powershell script as below:

# Filename: Hello.ps1
Write-Host
Write-Host 'Hello World!'
Write-Host "Good-bye World! `n"
# end of script













Then, we ran the exe to encrypt the script, it will create a .bin file which contain the encrypted script:




From the hello.bin, we cannot see what the code it is. To run the script,  just run exe with the bin file. The powershell script to run in a new powershell console.



Now let's see how the script works:

Below is the key point of the script:
  • Encryption/decryption key: The script have to run everywhere in the company and ran by anyone, so we have to put the key in the executable file. There two string to combine the key, one part is the file name and another part is the substring from a key string pool. So it is not easy to guess the key :). And we put company information as Initialization vector. So the different company will have different IV :)

 private static string getkey (string filename)    {           
       
        string passwordpool = "HKPpCy5@9CM9ufu^4ZrHw2i*b3zq@KL&Lm%ixc3i5ELn8VTMNJVekzPetic!z^W!QsnLL3qxUmefsCXT#vYoeEUu@FCt%X^vXmHAKRQqz*^Fq*oWG!cvMtPfJ6AFRSc2kS9gsE$esYNbPBtx38bd^NJpQU675hqm8CSiBaxjW6x8W45$!BSDYL!!&XMMNpKgMe8pHBQMA#TfMz&bVDPVZ7#NF3G!7a!UCVWczFu23Q!c4owgGzr2^mfaSZaMB#Jz;
        int keylength=16-filename.Length;
            string subkey=passwordpool.Substring(123,keylength);
            return subkey+filename;
       
        }

  • Run script from C#:
Method One:  We tried Powershell  RunspaceFactory.CreateRunspace() first : it is works great but user can only see the running output after the script finishes and we have some difficult for "Add-PSSnapin" for exchange, Vmware or SCOM. so we changed to method two:


  private static void runPSScript(string scriptText)
        {
            // create Powershell runspace
            Runspace runspace = RunspaceFactory.CreateRunspace();
            // open it
            runspace.Open();
            // create a pipeline and feed it the script text
            Pipeline pipeline = runspace.CreatePipeline();
            pipeline.Commands.AddScript(scriptText);
            pipeline.Commands.Add("Out-String");

            // execute the script
            Collection results = pipeline.Invoke();

            // close the runspace
            runspace.Close();

            // convert the script result into a single string
            StringBuilder stringBuilder = new StringBuilder();
            foreach (PSObject obj in results)
            {
                stringBuilder.AppendLine(obj.ToString());
            }
            Console.WriteLine(stringBuilder.ToString());

        }



Method two:  we use ProcessStartInfo to call powershell and pass the script as command. So you can see the script output from new powershell window. To stream the PS script to Powershell console, we uses powershell to decrypted the script to pass the commands in powershell


 private static void runPSScript2(string scriptText,string key,string salt)
        {
            string codestring = "$fsCrypt = new-object io.FileStream($path, 'Open');$r = new-Object System.Security.Cryptography.RijndaelManaged ;$key = [Text.Encoding]::UTF8.GetBytes($skey);$IV = [Text.Encoding]::UTF8.GetBytes($salt);$decryptor = $r.CreateDecryptor($key,$iv);$cs = new-Object Security.Cryptography.CryptoStream $fsCrypt,$decryptor,'Read';$sr = new-Object IO.StreamReader $cs;$script=$sr.ReadToEnd();$sr.Close();$cs.Close();$decryptor.Clear();$r.Clear();cls;Invoke-Expression $script";
            string parameter="$path='"+scriptText+"';$skey='"+key+"';$salt='"+salt+"';";
            string fullpscode = parameter + codestring;

            string command = scriptText;
            ProcessStartInfo cmdsi = new ProcessStartInfo("powershell.exe");
            cmdsi.Arguments = " -command "+fullpscode;
            Process cmd = Process.Start(cmdsi);
          

        }



Below is the full code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.IO;
using System.Security;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace ConsoleApplication1
{
    class Program
    {


        static void Main(string[] args)
        {

            if (args == null)
            {
                Console.WriteLine("Please add the bin file to run, for example: Runscript.exe srvreport.bin "); // Check for null array
            }
            else
            {
                string salt="XXXX Company";
                string filename = args[0];
                string[] Aextension = filename.Split(new string[] { "." }, 0);
                //Console.WriteLine(Aextension.Length.ToString());
                if (Aextension.Length > 1)
                {
                    string extension = Aextension[1];
                    if (extension == "ps1")
                    {
                        string outputfilename = filename.Replace(".ps1", ".bin");
                        string KeyedCollection=getkey(Aextension[0]);
                        EncryptFile(filename, outputfilename, KeyedCollection, salt);
                    }
                    else
                    {

                        if (extension == "bin")
                        {
                            Console.WriteLine(filename);
                            string KeyedCollection = getkey(Aextension[0]);
                            runPSScript2(filename, KeyedCollection, salt);
                        }
                    }
                }
                else {

                    filename = filename + ".bin";
                    string KeyedCollection = getkey(Aextension[0]);
                    runPSScript2(filename, KeyedCollection, salt);
                }






            }
        }

  

        private static void runPSScript2(string scriptText,string key,string salt)
        {
            string codestring = "$fsCrypt = new-object io.FileStream($path, 'Open');$r = new-Object System.Security.Cryptography.RijndaelManaged ;$key = [Text.Encoding]::UTF8.GetBytes($skey);$IV = [Text.Encoding]::UTF8.GetBytes($salt);$decryptor = $r.CreateDecryptor($key,$iv);$cs = new-Object Security.Cryptography.CryptoStream $fsCrypt,$decryptor,'Read';$sr = new-Object IO.StreamReader $cs;$script=$sr.ReadToEnd();$sr.Close();$cs.Close();$decryptor.Clear();$r.Clear();cls;Invoke-Expression $script";
            string parameter="$path='"+scriptText+"';$skey='"+key+"';$salt='"+salt+"';";
            string fullpscode = parameter + codestring;

            string command = scriptText;
            ProcessStartInfo cmdsi = new ProcessStartInfo("powershell.exe");
            cmdsi.Arguments = " -command "+fullpscode;
            Process cmd = Process.Start(cmdsi);
          

        }

   private static string getkey (string filename)    {          
      
        string passwordpool = "HKPpCy5@9CM9ufu^4ZrHw2i*b3zq@KL&Lm%ixc3i5ELn8VTMNJVekzPetic!z^W!QsnLL3qxUmefsCXT#vYoeEUu@FCt%X^vXmHAKRQqz*^Fq*oWG!cvMtPfJ6AFRSc2kS9gsE$esYNbPBtx38bd^NJpQU675hqm8CSiBaxjW6x8W45$!BSDYL!!&XMMNpKgMe8pHBQMA#TfMz&bVDPVZ7#NF3G!7a!UCVWczFu23Q!c4owgGzr2^mfaSZaMB#Jz;
        int keylength=16-filename.Length;
            string subkey=passwordpool.Substring(123,keylength);
            return filename+subkey;
      
        }


        private static string DecryptFile(string inputFile,string skey)
        {
             string result = "";
            var output = new MemoryStream();
            //FileStream fsOut = new FileStream("encrpout.txt", FileMode.Create);
            try
            {

                using (RijndaelManaged aes = new RijndaelManaged())
                {
                    byte[] key = ASCIIEncoding.UTF8.GetBytes(skey);

                    /* This is for demostrating purposes only.
                     * Ideally you will want the IV key to be different from your key and you should always generate a new one for each encryption in other to achieve maximum security*/
                    byte[] IV = ASCIIEncoding.UTF8.GetBytes(skey);

                    using (FileStream fsCrypt = new FileStream(inputFile, FileMode.Open))
                    {

                        using (ICryptoTransform decryptor = aes.CreateDecryptor(key, IV))
                        {
                            using (CryptoStream cs = new CryptoStream(fsCrypt, decryptor, CryptoStreamMode.Read))
                            {
                                int data;
                                while ((data = cs.ReadByte()) != -1)
                                {
                                    output.WriteByte((byte)data);
                                    // fsOut.WriteByte((byte)data);
                                }
                            }
                        }

                    }
                }
                output.Position = 0;
                StreamReader sr = new StreamReader(output);
                result = sr.ReadToEnd();
                //Console.Write(result);

            }
            catch (Exception ex)
            {
                // failed to decrypt file
            }

            return result;
        }

        private static void EncryptFile(string inputFile, string outputFile, string skey,string ssalt)
        {
            try
            {
                using (RijndaelManaged aes = new RijndaelManaged())
                {
                    byte[] key = ASCIIEncoding.UTF8.GetBytes(skey);

                    /* This is for demostrating purposes only.
                     * Ideally you will want the IV key to be different from your key and you should always generate a new one for each encryption in other to achieve maximum security*/
                    byte[] IV = ASCIIEncoding.UTF8.GetBytes(ssalt);

                    using (FileStream fsCrypt = new FileStream(outputFile, FileMode.Create))
                    {
                        using (ICryptoTransform encryptor = aes.CreateEncryptor(key, IV))
                        {
                            using (CryptoStream cs = new CryptoStream(fsCrypt, encryptor, CryptoStreamMode.Write))
                            {
                                using (FileStream fsIn = new FileStream(inputFile, FileMode.Open))
                                {
                                    int data;
                                    while ((data = fsIn.ReadByte()) != -1)
                                    {
                                        cs.WriteByte((byte)data);
                                    }
                                }
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex.ToString());
                // failed to encrypt file
            }
        }
    }





}

Tuesday, January 18, 2011

Exchange 2010 DR test failed and recovery DAG




On our Exchange 2010 DR test, we lost all the nodes in the cluster. Below the clear up steps which we remove the old setting and re-add the nodes, database back to DAG:


Get DAG info
Get-DAG shows node 1/3 are stopped node and node 2 is started but missing node4
Remove DAG
Remove-DatabaseAvailabilityGroupServer -Identity DAG01 -ConfigurationOnly:$TRUE -MailboxServer MBP01
WARNING: The operation wasn't successful because an error was encountered. You may find more details in log file  "C:\ExchangeSetupLogs\DagTasks\dagtask_2010-11-15_14-15-28.839_remove-databaseavailabiltygroupserver.log".
Mailbox server 'MBP01' cannot be removed from the database availability group because mailbox database 'DB01' has  multiple copies. Use Remove-MailboxDatabaseCopy either to remove the copy from this server or to remove the copies from other servers in the database availability group.
    + CategoryInfo          : InvalidArgument: (:) [Remove-DatabaseAvailabilityGroupServer], RemoveDagServer...icatedE
   xception
    + FullyQualifiedErrorId : C26FF955,Microsoft.Exchange.Management.SystemConfigurationTasks.RemoveDatabaseAvailabili
   tyGroupServer

[PS] C:\Windows\system32>Get-MailboxDatabaseCopyStatus *\MBP01

Name                                          Status          CopyQueue ReplayQueue LastInspectedLogTime   ContentIndex
                                                              Length    Length                             State       
----                                          ------          --------- ----------- --------------------   ------------
DB01\MBP01                              Dismounted      0         0                                  Failed     
DB02\MBP01                              Failed          0         0           11/12/2010 11:50:57 AM Failed     
DB03\MBP01                              Failed          0         0           11/12/2010 11:47:36 AM Failed     
DB07\MBP01                              Failed          0         0           11/12/2010 11:47:23 AM Failed     

Remove DB
Get-MailboxDatabaseCopyStatus *\MBP01

Name                                          Status          CopyQueue ReplayQueue LastInspectedLogTime   ContentIndex
                                                              Length    Length                             State      
----                                          ------          --------- ----------- --------------------   ------------
DB01\MBP01                              Dismounted      0         0                                  Failed     
DB02\MBP01                              Failed          0         0           11/12/2010 11:50:57 AM Failed     
DB03\MBP01                              Failed          0         0           11/12/2010 11:47:36 AM Failed     
DB07\MBP01                              Failed          0         0           11/12/2010 11:47:23 AM Failed     

Get-MailboxDatabaseCopyStatus *\MBP01 | Remove-MailboxDatabaseCopy

Get-MailboxDatabaseCopyStatus *\MBP03 | Remove-MailboxDatabaseCopy

Get-MailboxDatabaseCopyStatus *\MBP04 | Remove-MailboxDatabaseCopy

Get-MailboxDatabaseCopyStatus *\MBP02 | Remove-MailboxDatabaseCopy

Example log:

The database "DB01" is currently hosted on server "MBP01". Use Move-ActiveMailboxDatabase to move the active copy  of the database to a different server. You can use the Remove-MailboxDatabase task if this is the only copy.Confirm

Remove Server
Remove-DatabaseAvailabilityGroupServer -Identity DAG01 -ConfigurationOnly:$TRUE -MailboxServer MBP01

Remove-DatabaseAvailabilityGroupServer -Identity DAG01 -ConfigurationOnly:$TRUE -MailboxServer MBP02

Remove-DatabaseAvailabilityGroupServer -Identity DAG01 -ConfigurationOnly:$TRUE -MailboxServer MBP03

Remove-DatabaseAvailabilityGroupServer -Identity DAG01 -ConfigurationOnly:$TRUE -MailboxServer MBP04

Confirm
Are you sure you want to perform this action?
Removing Mailbox server "MBP01" from database availability group "DAG01".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [?] Help (default is "Y"): a
Clear up cluster
On each nodes:
cluster node /force
Attempting to clean up node '' ...
Clean up successfully completed.
Disable DAG computer account
In AD computer OU:
·         Disable tag01 computer object
·         Assign “Exchange trusted subsystem” full control to the object:


Remove failover cluster role

On each node:

servermanagercmd -r failover-clustering

Servermanagercmd.exe is deprecated, and is not guaranteed to be supported in fut
ure releases of Windows. We recommend that you use the Windows PowerShell cmdlets that are available for Server Manager.

Start Removal...
[Removal] Succeeded: [Failover Clustering] Failover Clustering.

Success: Removal succeeded.
Add servers back to DAG
Add-DatabaseAvailabilityGroupServer -Identity DAG01 -MailboxServer MBP01
Add-DatabaseAvailabilityGroupServer -Identity DAG01 -MailboxServer MBP02
Add-DatabaseAvailabilityGroupServer -Identity DAG01 -MailboxServer MBP03
Add-DatabaseAvailabilityGroupServer -Identity DAG01 -MailboxServer MBP04

Test Cluster
Move cluster resource group to each nodes:
cluster group "cluster group"  /moveto:mbp02

Group                Node            Status
-------------------- --------------- ------
cluster group        MBP02        Online


Get info
Get-MailboxDatabaseCopyStatus *
Name                                          Status          CopyQueue ReplayQueue LastInspectedLogTime   ContentIndex
                                                              Length    Length                             State      
----                                          ------          --------- ----------- --------------------   ------------
DB03\MBP02                              Mounted         0         0                                  Healthy    
DB04\MBP02                              Dismounted      0         0                                  Failed     
DB07\MBP04                              Mounted         0         0                                  Healthy    
DB08\MBP04                              Dismounted      0         0                                  Failed     
DB02\MBP04                              Dismounted      0         0                                  Failed     
DB06\MBP04                              Dismounted      0         0                                  Failed     
DB05\MBP03                              Mounted         0         0                                  Healthy    
DB01\MBP01                              Dismounted      0         0                                  Failed     
DR_DB08\UMP01                           Mounted         0         0                                  Healthy    
drdb01\UMP01                            Mounted         0         0                                  Healthy    
Mailbox Database 1150652460\UMP01          Dismounted      0         0                                  Failed     
dr_db02\UMP01                           Mounted         0         0                                  Healthy    
dr_db03\UMP01                           Mounted         0         0                                  Healthy    
dr_db04\UMP01                           Mounted         0         0                                  Healthy    
DR_DB07\UMP01                           Mounted         0         0                                  Healthy    
dr_db05\UMP01                           Mounted         0         0                                  Healthy    
DR_DB06\UMP01                           Mounted         0         0                                  Healthy    

Remove old database
·         Clean up all move request
Get-MoveRequest | Remove-MoveRequest
·         Move users to DR databae
Get-MailboxDatabase | Mount-Database
Get-Mailbox -Database DB08 | New-MoveRequest -TargetDatabase dr_db08
Get-MoveRequestStatistics
Get-MoveRequest | Remove-MoveRequest
·         Mount public folder database :
Get-PublicFolderDatabase | Mount-Database
·         Format all the database drives on each node
Create DB
Create the “Logs” folder in the each database folder and run commands:


new-mailboxdatabase -Server 'MBP01' -Name 'DB02' -EdbFilePath 'D:\MPS\DB02\DB02.EDB' -LogFolderPath 'D:\MPS\DB02\LOGS'

new-mailboxdatabase -Server 'MBP02' -Name 'DB03' -EdbFilePath 'D:\MPS\DB03\DB03.EDB' -LogFolderPath 'D:\MPS\DB03\LOGS'

new-mailboxdatabase -Server 'MBP02' -Name 'DB04' -EdbFilePath 'D:\MPS\DB04\DB04.EDB' -LogFolderPath 'D:\MPS\DB04\LOGS'

new-mailboxdatabase -Server 'MBP03' -Name 'DB05' -EdbFilePath 'D:\MPS\DB05\DB05.EDB' -LogFolderPath 'D:\MPS\DB05\LOGS'

new-mailboxdatabase -Server 'MBP03' -Name 'DB06' -EdbFilePath 'D:\MPS\DB06\DB06.EDB' -LogFolderPath 'D:\MPS\DB06\LOGS'

new-mailboxdatabase -Server 'MBP04' -Name 'DB07' -EdbFilePath 'D:\MPS\DB07\DB07.EDB' -LogFolderPath 'D:\MPS\DB07\LOGS'

new-mailboxdatabase -Server 'MBP04' -Name 'DB08' -EdbFilePath 'D:\MPS\DB08\DB08.EDB' -LogFolderPath 'D:\MPS\DB07\LOGS'

Mount DB
Get-MailboxDatabase | Mount-Database -Force

Add DB copy
Add-MailboxDatabaseCopy DB01 -MailboxServer mbp02
Add-MailboxDatabaseCopy DB02 -MailboxServer mbp04
Add-MailboxDatabaseCopy DB03 -MailboxServer mbp01
Add-MailboxDatabaseCopy DB04 -MailboxServer mbp03
Add-MailboxDatabaseCopy DB05 -MailboxServer mbp02
Add-MailboxDatabaseCopy DB07 -MailboxServer mbp01 -DomainController dc04
Troubleshooting
Get-MailboxDatabaseCopyStatus * | where{ $_.status -like "fail*"} |Suspend-MailboxDatabaseCopy

Get-MailboxDatabaseCopyStatus * | Suspend-MailboxDatabaseCopy
Get-MailboxDatabaseCopyStatus * | Update-MailboxDatabaseCopy –DeleteExistingFiles