Tuesday, July 24, 2012

Adding domain user as local admin immediatly after domain join

Here's a way to add a domain user as a local admin immediatly after joining the domain, without rebooting first:
using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;

class NativeMethods
{
    public const int LOGON32_LOGON_INTERACTIVE = 2;
    public const int LOGON32_LOGON_NETWORK = 3;
    public const int LOGON32_LOGON_BATCH = 4;
    public const int LOGON32_LOGON_SERVICE = 5;
    public const int LOGON32_LOGON_UNLOCK = 7;
    public const int LOGON32_LOGON_NETWORK_CLEARTEXT = 8;
    public const int LOGON32_LOGON_NEW_CREDENTIALS = 9;

    public enum SID_NAME_USE
    {
        SidTypeUser = 1,
        SidTypeGroup,
        SidTypeDomain,
        SidTypeAlias,
        SidTypeWellKnownGroup,
        SidTypeDeletedAccount,
        SidTypeInvalid,
        SidTypeUnknown,
        SidTypeComputer,
    }

    public struct LOCALGROUP_MEMBERS_INFO_0
    {
        public IntPtr PSID;
    }

    [DllImport("kernel32.dll")]
    public extern static bool CloseHandle(IntPtr hToken);

    [DllImport("advapi32.DLL", SetLastError = true)]
    public static extern int LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool LookupAccountName(
        string lpSystemName,
        string lpAccountName,
        [MarshalAs(UnmanagedType.LPArray)] byte[] Sid,
        ref uint cbSid,
        StringBuilder ReferencedDomainName,
        ref uint cchReferencedDomainName,
        out SID_NAME_USE peUse);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool LookupAccountSid(
        string lpSystemName,
        [MarshalAs(UnmanagedType.LPArray)] byte[] lpSid,
        StringBuilder lpName,
        ref uint cchName,
        StringBuilder lpReferencedDomainName,
        ref uint cchReferencedDomainName,
        out SID_NAME_USE peUse);

    [DllImport("netapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern int NetLocalGroupAddMembers(
        string servername,
        string groupname,
        uint level,
        ref LOCALGROUP_MEMBERS_INFO_0 buf,
        uint totalentries);
}

public class AddAdminUserHelper
{
    public static void AddAdminUser(string domain, string username, string password)
    {
        // Get built in administrators account name
        StringBuilder adminGroupName = new StringBuilder();
        uint adminGroupNameCapacity = (uint)adminGroupName.Capacity;
        StringBuilder referencedDomainName = new StringBuilder();
        uint referencedDomainNameCapacity = (uint)referencedDomainName.Capacity;
        NativeMethods.SID_NAME_USE eUse;
        byte[] adminGroupSid = new byte[] { 1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2 };
        if (!NativeMethods.LookupAccountSid(
            null,
            adminGroupSid,
            adminGroupName,
            ref adminGroupNameCapacity,
            referencedDomainName,
            ref referencedDomainNameCapacity,
            out eUse))
        {
            Console.WriteLine("LookupAccountSid failed with error " + Marshal.GetLastWin32Error());
            return;
        }

        // Get a security token needed to be able to afterwards query for the user's SID
        IntPtr token = IntPtr.Zero;
        if (NativeMethods.LogonUser(
            username,
            domain,
            password,
            NativeMethods.LOGON32_LOGON_NEW_CREDENTIALS,
            0,
            out token) == 0)
        {
            Console.WriteLine("LogonUser failed with error " + Marshal.GetLastWin32Error());
            return;
        }

        // Get user's SID
        byte[] userSid = new byte[1024];
        uint userSidLength = (uint)userSid.Length;
        referencedDomainName = new StringBuilder();
        referencedDomainNameCapacity = (uint)referencedDomainName.Capacity;
        NativeMethods.SID_NAME_USE peUse;
        using (WindowsImpersonationContext context = WindowsIdentity.Impersonate(token))
        {
            if (!NativeMethods.LookupAccountName(
                domain,
                username,
                userSid,
                ref userSidLength,
                referencedDomainName,
                ref referencedDomainNameCapacity,
                out peUse))
            {
                Console.WriteLine("LookupAccountName failed with error " + Marshal.GetLastWin32Error());
                return;
            }
        }
        NativeMethods.CloseHandle(token);

        // Add user's SID to local admins group
        IntPtr userSidNative = Marshal.AllocHGlobal(userSid.Length);
        Marshal.Copy(userSid, 0, userSidNative, (int)userSid.Length);
        NativeMethods.LOCALGROUP_MEMBERS_INFO_0 info0;
        info0.PSID = userSidNative;
        int r = NativeMethods.NetLocalGroupAddMembers(
            null,
            adminGroupName.ToString(),
            0,
            ref info0,
            1);
        Marshal.FreeHGlobal(userSidNative);
        if (r != 0)
        {
            Console.WriteLine("NetLocalGroupAddMembers failed by returning " + r);
            return;
        }
    }
}

Thursday, July 12, 2012

Join a domain and rename in one reboot using WMI

If you don't take special care in your script, then renaming a machine and then joining it to a Windows domain will join it using the old name. Apparently there's an undocumented flag 0x400 on JoinDomainOrWorkGroup. At least I think it started working when I added that flag... Here's a PowerShell snippet that's working for me now:
$computer = Get-WmiObject Win32_ComputerSystem
$cred = Get-Credential
$computer.Rename("newmachinename", $NULL, $NULL)
$computer.JoinDomainOrWorkGroup( `
 "mydomain", `
 ($cred.GetNetworkCredential()).Password, `
 $cred.UserName, `
 $NULL, `
 0x1 + 0x2 + 0x20 + 0x400)

Sunday, July 08, 2012

Windows Shares vs. FTP vs. FTPS vs. SFTP

Conducted this little test between a Windows 7 machine and Windows XP machine on a 100 Mbit switch. 

Used FileZilla server for FTP and FTPS server, FileZilla as FTP and FTPS client, Cygwin OpenSSH as SFTP server and WinSCP as SFTP client.

Was surprised to see how much overhead there was on FTPS, but it could also be something specific to FileZilla.

Windows sharesFTP FTPSSFTP
2700 files, 200Mb4,17 Mb/sec
48 secs
4,76 Mb/sec
42 secs
0,61 Mb/sec
326 secs
3,03 Mb/sec
66 secs
1 file, 550Mb9,82 Mb/sec
56 secs
11,22 Mb/sec
49 secs
11,00 Mb/sec
50 secs
6,25 Mb/sec
88 secs

Friday, July 06, 2012

Finding all machines a user owns in Active Directory

Here's a quick and dirty way to find all machines a user owns in Active Directory. Performance is not good though, as the objects are processed client side. I was not able to find any way of doing server side queries dealing with the ntSecurityDescriptor field. Please let me know if you find a way!

Adjust the DN strings as needed.

using System;
using System.DirectoryServices;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            DateTime startTime = DateTime.Now;

            DirectoryEntry d = new DirectoryEntry("LDAP://ou=UserAccounts,dc=contoso,dc=com");
            DirectorySearcher s = new DirectorySearcher(d, "(&(objectClass=user)(cn=John Smith))",
                new string[] { "cn", "objectsid" });

            string sid = BitConverter.ToString(
                    (byte[])s.FindOne().Properties["objectsid"][0])
                .Replace("-", "");

            int count = 0;
            d = new DirectoryEntry("LDAP://ou=Machines,dc=contoso,dc=com");
            s = new DirectorySearcher(d, "(&(objectCategory=computer)(objectClass=computer)(cn=*))",
                new string[] { "cn", "ntSecurityDescriptor" });
            s.PageSize = 500;
            s.SecurityMasks = SecurityMasks.Owner;
            foreach (SearchResult r in s.FindAll())
            {
                count++;
                string cn = (string)r.Properties["cn"][0];
                string sd = BitConverter.ToString((byte[])r.Properties["ntsecuritydescriptor"][0]).Replace("-", "");

                if (sd.Contains(sid))
                    Console.WriteLine(cn);
            }

            Console.WriteLine();
            Console.WriteLine("Checked " + count + " objects");
            Console.WriteLine("Query finished in " + (DateTime.Now - startTime));
        }
    }
}