Search code examples
c#powershellactive-directoryexchange-serverpowershell-remoting

How to add image to Active Directory user from C# using PowerShell remoting?


I'm writing a program in C# that adds a picture to an Active Directory user object (in the ThumbnailPhoto attribute). The program runs on another PC than the Exchange Server 2010. Using PowerShell remoting I'm able to, for example, enable a mailbox for the AD user.

When I execute the following commands in PowerShell (on another PC than the Exchange Server), it works perfectly:

enter-pssession exchange_server_fqdn
$cred = get-credential          ( domain\administrator , **** )
$sess = new-pssession -configurationName Microsoft.Exchange -ConnectionUri http://exchange_server_fqdn/PowerShell/ -Authentication Kerberos -Credential $cred
import-pssession $sess
Import-RecipientDataProperty -Identity ADusername -Picture -FileData ([Byte[]]$(Get-Content -Path 'C:\\person.jpg' -Encoding Byte -ReadCount 0))
remove-pssession $sess

But I need to execute the commands from C#. The following code runs (no exception is thrown), but the image isn't added to the AD user object:

string username = "xxx";
string password = "yyy";
string exchangeServer = "zzz"; // FQDN of Exchange Server 2010
string liveIdconnectionUri = "http://" + exchangeServer + "/Powershell?serializationLevel=Full";

// credentials
SecureString passwordSecureString = new SecureString();
foreach (char c in password) passwordSecureString.AppendChar(c);
PSCredential credential = new PSCredential(username, passwordSecureString);

WSManConnectionInfo connectionInfo = new WSManConnectionInfo(
    new Uri(liveIdconnectionUri),
    "http://schemas.microsoft.com/powershell/Microsoft.Exchange",
    credential);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;

PowerShell powershell = PowerShell.Create();
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace(connectionInfo);
runspace.Open();
powershell.Runspace = runspace;
PSCommand command = new PSCommand();

// Add ThumbnailPhoto attribute to Active Directory user
// This command works when executed on the Exchange Server 2010 in the Exchange Management Shell,
// But it doens't work when executed in C#.
command.AddScript("Import-RecipientDataProperty -Identity ADusername -Picture -FileData ([Byte[]]$(Get-Content -Path 'C:\\person.jpg' -Encoding Byte -ReadCount 0))");

powershell.Commands = command;
try {
    Collection<PSObject> commandResults = powershell.Invoke<PSObject>();
    foreach (PSObject result in commandResults) Console.WriteLine(result.ToString());
}
catch (Exception ex) {
    Console.WriteLine(ex.Message);
}

The only problem in the code is the line with command.AddScript. If I replace that line with, for example, this:

command.AddCommand("Get-Mailbox");
command.AddParameter("Identity", "ADusername");

... then it does work. Enabling a mailbox also works.

How can I execute the command to add an image to an Active Directory user object from C# (using PowerShell remoting)?

Working code based on accepted answer:

DirectoryEntry container = new DirectoryEntry(LDAP_URI + USERS_DIR);
DirectoryEntry user = container.Children.Add("cn=" + username, "user");

// Set other property's of the user object:
//// user.Properties ["xxx"].Value = "yyy";
//// ...

byte [] buffer;
FileStream fileStream = new FileStream(@"c:\photo.jpg", FileMode.Open, FileAccess.Read);

try {
    int length = (int) fileStream.Length;  // get file length
    buffer = new byte [length];            // create buffer
    int count;                             // actual number of bytes read
    int sum = 0;                           // total number of bytes read

    // read until Read method returns 0 (end of the stream has been reached)
    while ((count = fileStream.Read(buffer, sum, length - sum)) > 0)
        sum += count;  // sum is a buffer offset for next reading
}

finally {
    fileStream.Close();
}

user.Properties ["thumbnailPhoto"].Value = buffer;

user.CommitChanges();

Solution

  • I would take a different approach:

    byte[] pictureBytes = //Use a FileStream to read the data
    
    command.AddCommand("Import-RecipientPropertyData");
    command.AddParameter("Identity", "fooUser");
    command.AddParameter("Picture", "$true");
    command.AddParameter("Encoding", "Byte");
    command.AddParameter("ReadCount", 0);
    command.AddParameter(FileData, pictureBytes);
    

    Alternatively, you could just read the picture in to your pictureBytes and use ADSI (DirectoryEntry) to write directly to the thumbnailPhoto attribute. Just make sure that pictureBytes is <= 10KB as that's the max on the attribute in AD.