Monday 31 December 2012

Programmatically change the default sound device in Windows 7

I got a new monitor last week and it came with integrated speakers, which I didn't think I would use, but it occurred to me that it would be handy to use these speakers rather than my Logitech 5.1 system when browsing the internet. I normally have the speakers off at the sub woofer for one main reason, the system uses 30 Watts in stand by, which roughly equates to £30 a year in wasted electricity and since I normally use my desktop only to watch movies or TV series this is not a major inconvenience, but sometimes it's annoying. At any rate, I was looking for a way of changing the default  sound device using PowerShell, but it looks like it's not possible, however, it is possible to do it programmatically.

I'm reproducing the code here because I thought it was interesting. This is not my code, the original article can be found here.

PolicyConfig.h:
// ----------------------------------------------------------------------------
// PolicyConfig.h
// Undocumented COM-interface IPolicyConfig.
// Use for set default audio render endpoint
// @author EreTIk
// ----------------------------------------------------------------------------
 
#pragma once
 
interface DECLSPEC_UUID("f8679f50-850a-41cf-9c72-430f290290c8")
IPolicyConfig;
class DECLSPEC_UUID("870af99c-171d-4f9e-af0d-e63df40c2bc9")
CPolicyConfigClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigClient
// {870af99c-171d-4f9e-af0d-e63df40c2bc9}
//
// interface IPolicyConfig
// {f8679f50-850a-41cf-9c72-430f290290c8}
//
// Query interface:
// CComPtr[IPolicyConfig] PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigClient));
//
// @compatible: Windows 7 and Later
// ----------------------------------------------------------------------------
interface IPolicyConfig : public IUnknown
{
public:
 
    virtual HRESULT GetMixFormat(
        PCWSTR,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
        PCWSTR,
        INT,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(
        PCWSTR
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
        PCWSTR,
        WAVEFORMATEX *,
        WAVEFORMATEX *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
        PCWSTR,
        INT,
        PINT64,
        PINT64
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
        PCWSTR,
        PINT64
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
        __in PCWSTR wszDeviceId,
        __in ERole eRole
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
        PCWSTR,
        INT
    );
};
 
interface DECLSPEC_UUID("568b9108-44bf-40b4-9006-86afe5b5a620")
IPolicyConfigVista;
class DECLSPEC_UUID("294935CE-F637-4E7C-A41B-AB255460B862")
CPolicyConfigVistaClient;
// ----------------------------------------------------------------------------
// class CPolicyConfigVistaClient
// {294935CE-F637-4E7C-A41B-AB255460B862}
//
// interface IPolicyConfigVista
// {568b9108-44bf-40b4-9006-86afe5b5a620}
//
// Query interface:
// CComPtr[IPolicyConfigVista] PolicyConfig;
// PolicyConfig.CoCreateInstance(__uuidof(CPolicyConfigVistaClient));
//
// @compatible: Windows Vista and Later
// ----------------------------------------------------------------------------
interface IPolicyConfigVista : public IUnknown
{
public:
 
    virtual HRESULT GetMixFormat(
        PCWSTR,
        WAVEFORMATEX **
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(
        PCWSTR,
        INT,
        WAVEFORMATEX **
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(
        PCWSTR,
        WAVEFORMATEX *,
        WAVEFORMATEX *
    );
 
    virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(
        PCWSTR,
        INT,
        PINT64,
        PINT64
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(
        PCWSTR,
        PINT64
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE SetShareMode(
        PCWSTR,
        struct DeviceShareMode *
    );  // not available on Windows 7, use method from IPolicyConfig
 
    virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(
        PCWSTR,
        const PROPERTYKEY &,
        PROPVARIANT *
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(
        __in PCWSTR wszDeviceId,
        __in ERole eRole
    );
 
    virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(
        PCWSTR,
        INT
    );  // not available on Windows 7, use method from IPolicyConfig
};
Console App Code:

#include "stdio.h"
#include "wchar.h"
#include "tchar.h"
#include "windows.h"
#include "Mmdeviceapi.h"
#include "PolicyConfig.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
 
HRESULT SetDefaultAudioPlaybackDevice(LPCWSTR devID)
{
 IPolicyConfigVista *pPolicyConfig;
 ERole reserved = eConsole;
 
    HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient),
  NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID *)&pPolicyConfig);
 if (SUCCEEDED(hr))
 {
  hr = pPolicyConfig->SetDefaultEndpoint(devID, reserved);
  pPolicyConfig->Release();
 }
 return hr;
}
 
// EndPointController.exe [NewDefaultDeviceID]
int _tmain(int argc, _TCHAR* argv[])
{
 // read the command line option, -1 indicates list devices.
 int option = -1;
 if (argc == 2) option = atoi((char*)argv[1]);
 
 HRESULT hr = CoInitialize(NULL);
 if (SUCCEEDED(hr))
 {
  IMMDeviceEnumerator *pEnum = NULL;
  // Create a multimedia device enumerator.
  hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
   CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnum);
  if (SUCCEEDED(hr))
  {
   IMMDeviceCollection *pDevices;
   // Enumerate the output devices.
   hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
   if (SUCCEEDED(hr))
   {
    UINT count;
    pDevices->GetCount(&count);
    if (SUCCEEDED(hr))
    {
     for (int i = 0; i < count; i++)
     {
      IMMDevice *pDevice;
      hr = pDevices->Item(i, &pDevice);
      if (SUCCEEDED(hr))
      {
       LPWSTR wstrID = NULL;
       hr = pDevice->GetId(&wstrID);
       if (SUCCEEDED(hr))
       {
        IPropertyStore *pStore;
        hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);
        if (SUCCEEDED(hr))
        {
         PROPVARIANT friendlyName;
         PropVariantInit(&friendlyName);
         hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
         if (SUCCEEDED(hr))
         {
          // if no options, print the device
          // otherwise, find the selected device and set it to be default
          if (option == -1) printf("Audio Device %d: %ws\n",i, friendlyName.pwszVal);
          if (i == option) SetDefaultAudioPlaybackDevice(wstrID);
          PropVariantClear(&friendlyName);
         }
         pStore->Release();
        }
       }
       pDevice->Release();
      }
     }
    }
    pDevices->Release();
   }
   pEnum->Release();
  }
 }
 return hr;
}
As mentioned above this is not my code, copied from here.

Wednesday 26 December 2012

Unable to activate Windows 8 - 0x8007232B - DNS name does not exist.

So I finally decided to install Windows 8 on one of the work desktops and I could not activate it, I would get this error.
Error code: 0x8007232B
Error description: DNS name does not exist.
It turns out that because I had used the MSDN iso to install this machine I had to change the license as it seems that Windows 8 gets installed with a temporary key when using the MSDN isos.

In order to change the license, run the following command (from a command prompt or PowerShell) with elevated permissions (Run as Administrator):
slmgr.vbs /ipk mypro-ductk-key00-00000
Now Windows 8 can be activated.

Friday 21 December 2012

Slow vs Fast Hashing Algorithms in C#

In a project I'm working on at the moment the passwords were hashed using the MD5 hash, in fact the code is pretty much line for line the code found here, one problem with this code. 

There is no salting, which means that users using the same passwords would have the same password, e.g. QWERT12345 will always hash to bb760a3ee6d5abe0b0e58d7977d25527.

A bigger problem, this hashing algorithm is incredibly fast. My ancient Intel p8600 @ 2.4 GHz takes about 18 seconds to calculate 1 million of these hashes, hashing QWERT12345, and this is running on a single core and this is absolutely nothing to write home about, compared to what a GPU can do, see this article.

There are quite a few options to solve this problem, namely using a more secure way of hashing passwords, but I chose using PBKDF2, mainly because it's part of the framework.

This method allows changes to the number of iterations and/or the salt length. Ensure that the separator is not part of the base64 character set, namely don't use +,/ or =.

Here is the code I used:

public const int keyLength = 32;
public const int iterations = 10000;
public string PBKDF2Password(string password)
{
    StringBuilder sb = new StringBuilder();

    using (var bytes = new Rfc2898DeriveBytes(password, keyLength, iterations))
    {
        byte[] salt = bytes.Salt;
        byte[] key = bytes.GetBytes(keyLength);

        sb.Append(string.Format("{0}:", iterations));
        sb.Append(string.Format("{0}:", Convert.ToBase64String(salt)));
        sb.Append(string.Format("{0}", Convert.ToBase64String(key)));

    }

    return sb.ToString();
}

Using this method, with the above values, it takes my laptop 0.3 seconds to calculate a single salted hash, which roughly means that we've gone from 50k hashes per second to 3 per second. The beauty of this hashing algorithm is that you can increase the number of iterations as hardware gets more and more powerful, so now you just need to decide how long you want your users to wait before they can log in to your system.

A method like this can be used for checking the password for validity.

public bool CheckPassword(string password, string hash)
{
    bool result = false;
    byte[] saltArray, key;
    int iterations;

    string[] hashComponents = hash.Split(':');

    iterations = Convert.ToInt32(hashComponents[0]);
    saltArray = Convert.FromBase64String(hashComponents[1]);
    key = Convert.FromBase64String(hashComponents[2]);

    using (var bytes = new Rfc2898DeriveBytes(password, saltArray, iterations))
    {
        byte[] newKey = bytes.GetBytes(keyLength);

        if (newKey.SequenceEqual(key))
        {
            result = true;
        }
    }
    return result;
}

Monday 17 December 2012

Start (and Stop) Microsoft Dynamics CRM 2011 Services using PowerShell

Today I spent a good deal of time trying to sort out some MS Dynamics CRM 2011 issues and  I had to start some services time and time again, so I wondered whether I could just script the whole thing and it turns out that you can, but a one liner will do to restart all MS Dynamics CRM services.

get-service | where {$_.Name -like "*CRM*" -or $_.Name -like "W3SVC"}| ForEach-Object {Restart-Service –DisplayName $_.DisplayName}

As you can probably imagine there is a Start-Service and a Stop-Service cmdlet in PowerShell.

Friday 14 December 2012

Set User Regional Settings in Microsoft Dynamics CRM 2011

A few months ago we had an issue where all test users had US regional settings, which was a bit of a problem since the users could not change their own settings, customer requirement, so I used the method below to set it right. 

Two things to bear in mind:
  1. If you are looping through all users, make sure that you filter out SYSTEM and INTEGRATION users, better safe than sorry.
  2. You can avoid this (wrong regional settings) by setting the system regional settings to your desired setting before adding the users (not an option if an upgrade, but it's worth bearing in mind).
It might be a good idea just to pass the user id rather than the whole entity, but there you go.

private void SetUserSettingsToUK(IOrganizationService service, Entity user)
{
      Guid userId = new Guid();

      RetrieveUserSettingsSystemUserRequest settingsReq = new RetrieveUserSettingsSystemUserRequest();
      RetrieveUserSettingsSystemUserResponse settingsRsp = new RetrieveUserSettingsSystemUserResponse();

      settingsReq.ColumnSet = new ColumnSet(true);

      UpdateUserSettingsSystemUserRequest settingsUpdReq = new UpdateUserSettingsSystemUserRequest();
      UpdateUserSettingsSystemUserResponse settingsUpdRsp = new UpdateUserSettingsSystemUserResponse();

      Entity userSettings = new Entity("usersettings");
      userId = user.Id;

      settingsReq.EntityId = userId;
      settingsReq.ColumnSet = new ColumnSet(true);

      settingsRsp = (RetrieveUserSettingsSystemUserResponse)service.Execute(settingsReq);

      userSettings = (Entity)settingsRsp.Entity;

      //helplanguageid appears not to be set so we don't check for it.

      if (userSettings.Attributes["currencysymbol"].ToString() != "£"

   || Convert.ToInt32(userSettings.Attributes["localeid"]) != 2057
   || userSettings.Attributes["timeformatstring"].ToString().ToLower() != "hh:mm"
   || userSettings.Attributes["dateformatstring"].ToString().ToLower() != "dd/mm/yyyy"
   || Convert.ToInt32(userSettings.Attributes["uilanguageid"]) != 1033
   || Convert.ToInt32(userSettings.Attributes["longdateformatcode"]) != 1
   || Convert.ToInt32(userSettings.Attributes["negativecurrencyformatcode"]) != 1)
      {
        userSettings.Attributes["dateformatstring"] = "dd/MM/yyyy";
        userSettings.Attributes["timeformatstring"] = "HH:mm";
        userSettings.Attributes["currencysymbol"] = "£";
        userSettings.Attributes["negativecurrencyformatcode"] = 1;
        userSettings.Attributes["longdateformatcode"] = 1;
        userSettings.Attributes["localeid"] = 2057;
        userSettings.Attributes["helplanguageid"] = 1033;
        userSettings.Attributes["uilanguageid"] = 1033;

        settingsUpdReq.Settings = userSettings;
        settingsUpdReq.UserId = userId;
        try
        {

          settingsUpdRsp = (UpdateUserSettingsSystemUserResponse)service.Execute(settingsUpdReq);

        }
        catch (Exception ex)
        {
          StringBuilder sb = new StringBuilder();
          sb.AppendLine(string.Format("Unable to set user settings for user {0}.", user["fullname"]));
          sb.AppendLine(string.Format("Exception:{0}.", ex.Message));
          if (ex.InnerException != null)
          {
            sb.AppendLine(string.Format("InnerException: {0}", ex.InnerException.Message));
          }
          log.Debug(sb.ToString());
          log.Debug(sb.ToString());

        }

      }

    }

Sunday 9 December 2012

Set Regional Settings for Sharepoint 2010 Sites

Somebody at work forgot to change the regional settings to UK and since we have quite a few sites on our Sharepoint installation, I thought it would be a good idea to script the procedure rather than do it manually, so here it is:

The script takes a single parameter, which is the SharePoint base Url and sets all sites within to UK regional settings, change the GetCultureInfo line to your Locale.

 param ($url) 

 $snapin ="Microsoft.sharepoint.powershell" 

 if (-not $url) 
 { 
 write-host "Please Invoke script like this:  SetRegionalSettings.ps1 -url `"http://sp.dev.com/`"" 
 exit 
 } 

 if ( (Get-PSSnapin -Name $snapin -ErrorAction SilentlyContinue) -eq $null ) 
 {     
  Add-PSSnapin $snapin -ErrorAction SilentlyContinue 
 } 
 
 try 
 { 
  $sites = Get-SPweb -site $url -limit all 
 } 
 catch 
 { 
  Write-Error "An error occurred while retrieving the sharepoint sites for $url" 
 } 
 if ($sites -ne $null) 
 { 
  foreach ($site in $sites) 
  { 
   try 
    { 
     $site.Locale = [System.Globalization.CultureInfo]::GetCultureInfo("en-GB");    
     Write-Host "Changing Regional Settings for Site $($site.Name) to $($site.Locale.DisplayName)"   
     $site.Update() 
    } 
    catch  
    { 
     Write-Error "An error occurred while changing regional settings" 
    } 
  } 
 } 

Monday 3 December 2012

SSL Mutual (Two-way) Authentication for WCF services in IIS 7.5 using client certificates

In a previous post, I described  how to configure SSL client Authentication in IIS 7.5 using a One-to-One Mapping.

In this post I describe how to set up a client authenticated WCF web service.

Firstly, there are three pre-requisites.
  1. Server Certificate from trusted CA.
  2. Client Certificate from trusted CA.
  3. WCF service deployed to a website that has been configured to use SSL, see this post for details on how to configure a website to use SSL.
I would suggest following my excellent series on CAs, which starts here, but alas it's mostly oriented for IIS 6, so it's not exactly terribly useful, it does create a CA which is the basis but not much more. Similarly, this post details the usage of makecert to create self-signed certificates but again it's geared towards IIS 6, the certificate generation commands will work though. You can also use openSSL, details here to create self-signed certificates.

In any case, the first step is to ensure that the website is set to require SSL and client certificates:


The second step is to enable Anonymous Authentication. This might sound like a bad idea and to a certain extent it is, using a one to one mapping is a better idea, but I've not got that working yet. What is achieved with this configuration is that any user that has a client certificate from a trusted CA will be be able to use the WCF service.



Third step is to change the configuration file of the WCF service to this (Use your own name and Contract):

  <system.serviceModel>
    <bindings>
      <wsHttpBinding>
        <binding name="TransportSecurity">
          <security mode="Transport">
            <transport clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service name="API.Service" behaviorConfiguration="behaviour">
        <endpoint name="AnEndPoint" address="" binding="wsHttpBinding"
               contract="API.IService"  bindingConfiguration="TransportSecurity">
        </endpoint>
       </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="behaviour">
          <serviceMetadata   httpsGetEnabled="true"/>
          <serviceDebug includeExceptionDetailInFaults="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

And finally the client that consumes the web service needs to have this on the configuration file:

<system.serviceModel> 
 <bindings> 
  <wsHttpBinding> 
  <binding name="TransportSecurity"> 
   <security mode="Transport"> 
    <transport clientCredentialType="Certificate" /> 
   </security> 
  </binding> 
  </wsHttpBinding> 
 </bindings> 
 <client> 
  <endpoint address="https://fqdn/service.svc" behaviorConfiguration="behaviour" 
  binding="wsHttpBinding" bindingConfiguration="TransportSecurity" 
  contract="API.IService" name="TransportSecurity" /> 
 </client> 
 <behaviors> 
  <endpointBehaviors> 
   <behavior name="behaviour"> 
    <clientCredentials> 
     <clientCertificate findValue="577aeb8b603af8453b80c55e9ac4abdfd4cf9c6c" 
     storeLocation="CurrentUser" x509FindType="FindByThumbprint" />
    </clientCredentials> 
   </behavior> 
  </endpointBehaviors> 
 </behaviors> 
</system.serviceModel>

Note that you can also use a browser to test this.

Saturday 1 December 2012

We are where we are


A few weeks back somebody responded to an email where I complained about a few things that the project should have done, with the classic, we are where we are.

This was my reply. Nothing like a bit of quantum mechanics (Copenhagen interpretation style) in the morning:

We ALWAYS are where we are, how could it possibly be otherwise?

At best we could be in all possible places (should really be states) at once, if we happen to be microscopic (probably nanoscopic or even picoscopic would be more accurate) and only as long as nobody tries to measure where we are (measure our state). But this (measuring of our state) would cause the wave function to collapse into where we actually are (the state the system is in), thus breaking the superposition of states (being in all possible places at once).