0

I am creating an exchange user (new-mailbox) and then setting some AD parameters on them after the user is created in the same runspace with commands that will not run in the Exchange runspace unless import-module 'activedirecty' is ran. Is there a way to import the module after the runspace is created as I can do with the Powershell prompt?

inside the same runspace session I want to run:

new-mailbox
set-mailbox
set-user
set-aduser 

The last one is what requires me to import the AD module I can successfully run the command inside of Powershell directly, but can't seem to figure out how to add the module mid runspace session? I'd tried

powershell.AddParameter("import-module -name 'activedirectory'; set-aduser xxxx")

and

powershell.AddParameter("import-module -name 'activedirectory'")
powershell.AddParameter("set-aduser xxxx")

and

powershell.AddScript("import-module -name 'activedirectory'; set-aduser xxxx")

This works below

public void SetPasswordNeverExpiresProperty(bool PasswordNeverExpires, string alias)
    {           
        string dn = "CN=xxx,OU=xxx,OU=xxx=xxx=xxx=xxx,DC=xx,DC=xx,DC=xxx,DC=xxx"

        DirectoryEntry objRootDSE = new DirectoryEntry("LDAP://" + dn);
        ArrayList props = new ArrayList();
        int NON_EXPIRE_FLAG = 0x10000;
        int EXPIRE_FLAG = 0x0200;
        int valBefore = (int) objRootDSE.Properties["userAccountControl"].Value;            
        objRootDSE.Properties["userAccountControl"].Value = EXPIRE_FLAG;
        objRootDSE.CommitChanges();
        string valAfter = objRootDSE.Properties["userAccountControl"].Value.ToString();`

And I'm out of guesses, any help would be appreciated.

        PSCredential ExchangeCredential = new PSCredential(PSDomain + @"\" + PSUsername, PSpwd);
        WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("xxxxxx/powershell"), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", ExchangeCredential);
        connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;


        using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
        {
            PowerShell powershell = PowerShell.Create();

                if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
                {
                    // do nothing
                }
                else
                {
                    runspace.Open();
                    powershell.Runspace = runspace;
                }
            try
                {
                    psobjs = powershell.Invoke();
                }
                catch (Exception ex)
                {
                    result = "Failed: " + ex.Message;
                }

                powershell.Commands.Clear();

        }

enter image description here

Bbb
  • 517
  • 6
  • 27
  • I don't see where you are setting the Runspace property on the PowerShell object i.e. `$powershell.Runspace = $runspace` – Bruce Payette Apr 10 '18 at 16:58
  • I've added the code that I'm using to do that. I should also mention that the first 3 commands work correctly with the current code, the only one I'm having trouble with is the one dependent on the ActiveDirectory module. – Bbb Apr 10 '18 at 17:57
  • Can you put the actual code in-line (i.e. where it says //code here)? Looking at the fragments, this `powershell.AddParameter("import-module -name 'activedirectory'")` is wrong and won't work but this `powershell.AddScript("import-module -name 'activedirectory'; set-aduser xxxx")` should work. However the preferred way would be to do `powershell.AddCommand("Import-Module").AddParameter("Name", "ActiveDirectory")`. What happens with you do the `Import-Module` -do you get an error? And are you sure that the ActiveDirectory module is available on the Exchange server (as opposed to your local box) – Bruce Payette Apr 10 '18 at 18:37
  • The code fails at psobjs = powershell.Invoke(); Failed: The term ' Set-AdUser' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again. I've edited what you've requested. – Bbb Apr 10 '18 at 18:58
  • @Bruce-Payette , A picture has been added from Visual Studio, hopefully this helps with the error identification. The particular code I used to generate that is what you suggested ... `powershell.AddCommand("Import-Module").AddParameter("Name", "ActiveDirectory"); powershell.AddCommand("Set-AdUser");` – Bbb Apr 10 '18 at 19:11
  • 1
    I found that too: You can't use `Import-Module` when using remote PowerShell like that. You may be better off to use `DirectoryEntry` instead of PowerShell to modify the AD account, but the problem is replication (if you have more than one domain controller). If you do have more than one DC, you have to make sure you do both things on the same one. – Gabriel Luci Apr 10 '18 at 19:26
  • @GabrielLuci that is exactly why I'm shoe-horned into this situation. We have over 1500 DC's (yes fifteen hundred) and propagation is disastrous. I found a clever way to get around that, you can pull a property from the results of the command `psobjs = powershell.Invoke();` and grab a property called OriginatingServer, so `string originalDomaincontroller = psobjs.OriginatingServer` and set -domaincontroller or -server (depending on the command you run afterwards) to that value as long as the runspace is reused. I could connect to that DC and use a DirectoryEntry function I guess – Bbb Apr 10 '18 at 19:41
  • If you already know the DC, then that's the hard part already done. It doesn't look like `New-Mailbox` gives you the `distinguishedName` of the new account though. But you can use `new DirectoryEntry("LDAP://{domainController}")` and use that as the `SearchRoot` for `DirectorySearcher` to find the DN. I think you'll still have to use `new DirectoryEntry("LDAP://{domainController}/{distinguishedName}")` to finally bind to the new AD object. – Gabriel Luci Apr 10 '18 at 19:55
  • I built an automated account creation service for our environment several years ago (AD and Exchange 2010). I think this is why I actually created the AD account with `DirectoryEntry`, then used `Enable-Mailbox` (and passed the `DomainController` parameter) to create the mailbox after. – Gabriel Luci Apr 10 '18 at 19:58
  • If you're wondering, you can get the current domain controller from `DirectoryEntry` by using `.Options.GetCurrentServerName()` – Gabriel Luci Apr 10 '18 at 20:05
  • Thanks, I'm still trying to find a good DE example where I can check the value of the PasswordNeverExpires property at the moment. – Bbb Apr 10 '18 at 20:27
  • You set the `userAccountControl` attribute: https://stackoverflow.com/questions/11025495/set-windows-ad-password-so-that-it-never-expires – Gabriel Luci Apr 10 '18 at 21:08
  • Thank you @GabrielLuci, I just got it working when you posted that with the flags and all. Good call! Is there a way to control the DC you connect to with DirectoryEntry? – Bbb Apr 10 '18 at 21:12
  • Yeah, just include the fully qualified DC name with the LDAP path, like this: `new DirectoryEntry("LDAP://dc1.domain.com/CN=user1,OU=Users,DC=domain,DC=com")` – Gabriel Luci Apr 10 '18 at 21:32

1 Answers1

1

I'll sum up my comments in an answer, since it seems I was unexpectedly helpful :)

I also had found that you can't use Import-Module when using remote PowerShell like that. It's kind of annoying, but such is life.

Years ago, I implemented an automatic account creation service in our environment for AD and Exchange 2010. I found I had to do the AD account manipulation with DirectoryEntry and then only the Exchange stuff with PowerShell.

The problem is making sure that both things happen on the same domain controller so you don't run into replication problems.

So you have two options: Use New-Mailbox to create the mailbox and AD account in one shot. As you pointed out, the OriginatingServer property of the result has the domain controller. But there is also a DistinguishedName property there too! (I just found this when you mentioned the server property) Then you can create a DirectoryEntry object against the same domain controller like this:

new DirectoryEntry($"LDAP://{domainController}/{distinguishedName}")

Or, what I did (I think because I didn't realize at the time that I could get the DC from the result of New-Mailbox), is create the AD object first with DirectoryEntry, pull the domain controller it got created on from .Options.GetCurrentServerName(), then pass that in the DomainController parameter to Enable-Mailbox.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
  • Strangely enough, when I don't use the domain controller in the LDAP path, I can set the passwordexpires flag, but when I do, I'm able to read it but not set it with a result of "The server is unwilling to process the request" . Do you think it's a permissions issue? – Bbb Apr 11 '18 at 14:06
  • "passwordexpires" isn't actually an attribute in AD. If you are trying to force a password change on next logon, then you set the `pwdLastSet` attribute to 0. – Gabriel Luci Apr 11 '18 at 14:22
  • Or were you talking about a flag on the `userAccountControl` attribute? [This](https://msdn.microsoft.com/en-us/library/aa772300(v=vs.85).aspx) says that the `ADS_UF_PASSWORD_EXPIRED` flag is "created by the system using data from the Pwd-Last-Set attribute and the domain policy. It is read-only and cannot be set." That's probably why it's throwing that error. – Gabriel Luci Apr 11 '18 at 14:30
  • Correct, the useraccountcontrol attribute allows you to set a flag of 0x100000 which means the password never expires. My ldap path appears as follows: `"LDAP://AB-CD-EF-01.xxx.xxx.xxx.com/CN=||Equipment,OU=TESTMB,OU=ABCD,OU=DEFG,OU=HIJK,OU=LMNO,DC=xxx,DC=xxx,DC=xxxx,DC=xxx"` With that I can read, but not write/commit. Does it look formatted correctly? – Bbb Apr 11 '18 at 14:49
  • Maybe this is just a typo in your comment, but `0x100000` is `ADS_UF_NOT_DELEGATED`, whereas `ADS_UF_DONT_EXPIRE_PASSWD` is `0x10000` – Gabriel Luci Apr 11 '18 at 15:00
  • Your account name actually does start with two pipe characters? `||Equipment` Otherwise it looks right. – Gabriel Luci Apr 11 '18 at 15:02
  • Yes that was a typo, it should be 0x10000. Yes the account does start that way, I think the issue is that the equipment accounts are disabled, so it won't allow me to set that attribute. I'm testing it now after propagation occurs on 1500 or so DC's. Thanks for continuing to respond :) – Bbb Apr 11 '18 at 15:10
  • Yes, they do start out disabled (at least I know when they're created by `DirectoryEntry` they are). You have to set `userAccountControl` to `ADS_UF_NORMAL_ACCOUNT` (`0x200`) to enable it. – Gabriel Luci Apr 11 '18 at 15:16