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));
        }
    }
}

Tuesday, June 26, 2012

Compiling Bootstrap LESS on Windows

The current version of Bootstrap can neither be compiled on WinLess nor lessc.exe as of writing. I found that these days the easiest is actually to run the Windows version of Node.js. Standalone single .exe versions of Node.js are available. Go download the less.js source and you can make yourself a "lessjs.cmd" with something like the following:
d:\utils\node.exe d:\utils\lesscss\bin\lessc %1

Tuesday, May 01, 2012

Django as a micro framework, including models

The following can be used as a base when you want to avoid the normal structure Django imposes on your project, and you just want everything in a single file.

It is based on similar attempts by Ivan Sagalaev and Fahrzin Hemmati (especially this gist), and a post on stack overflow.

A thing that I consider an improvement over Fahrzin Hemmati's version is that models don't need the explicit app_label and __module__.

#
# Django as a microframework skeleton, by Allan Boll, 1 May 2012
# Based on http://softwaremaniacs.org/blog/2011/01/07/django-micro-framework/en/
# and https://gist.github.com/2219751
#

#
# Settings
#
appname = 'app'
from django.conf import settings
if not settings.configured:
    settings.configure(
        SITE_ID=1,
        TEMPLATE_DIRS=['.'],
        DATABASES = {
            'default':{
                'ENGINE':'django.db.backends.sqlite3',
                'NAME': 'db',
            }
        },
        DEBUG=True,
        TEMPLATE_DEBUG=True,
        ROOT_URLCONF = __name__,
        STATIC_URL='/static/',
        STATICFILES_DIRS=['./static/'],
        INSTALLED_APPS=(
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'django.contrib.admin', 
        )
    )

#
# Models
#
from django.db import models

# Workaround to allow models in current file
import sys
sys.modules[appname+'.'] = sys.modules[__name__]
old_module_name = sys.modules[__name__].__name__
sys.modules[__name__].__name__ = appname+'.'

class SomeModel(models.Model):
    field_name = models.CharField(max_length=10)

# Continuation of workaround to allow models in current file
sys.modules[__name__].__name__ = old_module_name


#
# Views
#
from django.shortcuts import render

def index(request):
    out = []
    for obj in SomeModel.objects.all():
        out.append(obj.field_name)
    return render(request, 'index.html', {'s': ', '.join(out)})


#
# URL configuration
#
from django.conf.urls.defaults import patterns, url, include

urlpatterns = patterns('',
    (r'^$', index),
)


#
# Admin site
#
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin, GroupAdmin
from django.contrib.auth.models import User, Group

admin.site = admin.AdminSite()
admin.site.register(User, UserAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(SomeModel)

urlpatterns = patterns('',
    url(r'^admin/', include(admin.site.urls))
) + urlpatterns


#
# Running
#
if __name__=='__main__':
    # Monkey patch get_app to allow models in current file
    get_app_orig = models.get_app
    def get_app(app_label,*a, **kw):
        if app_label==appname:
            return sys.modules[__name__]
        return get_app_orig(app_label, *a, **kw)
    models.get_app = get_app

    # Add models in current file to global list of apps
    models.loading.cache.app_store[type(appname+'.models',(),{'__file__':__file__})] = appname

    from django.core import management
    management.execute_from_command_line()

Friday, February 10, 2012

Sublime Text default to empty file

I recently began using the editor Sublime Text. Previously I was using Notepad++ which is also a fine editor, but not as "futuristic" as Sublime Text. I had gotten used to Notepad++'s behaviour of defaulting to an empty file when all tabs are closed, and found that this behaviour was easy to achieve in Sublime Text as well, thanks to its amazing API. To get the behaviour, drop the following Python source into something like Data/Packages/User/default_to_empty_file.py.


import sublime, sublime_plugin

class DefaultToEmptyFile(sublime_plugin.EventListener):
def on_close(self, view):
for window in sublime.windows():
if window == None:
continue

if len(window.views()) <= 1:
window.new_file()

def on_load(self, view):
window = view.window()
if window == None:
return

views = window.views()
if len(views) == 2:
for v in views:
if v != view and v.file_name() == None and v.size() == 0:
window.focus_view(v)
window.run_command('close_file')