Scripting WMI Namespace Security (part 3 of 3)


In the second part of this series, we discussed how to
retrieve the current security settings for a WMI namespace.   For
this blog post, I’ll show a Powershell script for modifying the current
security descriptor of a WMI namespace. 
Note that everything I’m doing in the Powershell script can be done in
vbscript, but I’ll leave that as an exercise to the reader.  Here’s the entire script.  I’ll discuss the various sections to explain
why I did something.  Note that this is
not intended to be a discussion about Powershell, so I’m focusing on the WMI
specific parts.

# Copyright
(c) Microsoft Corporation.  All rights
reserved. 

# For
personal use only.  Provided AS IS and
WITH ALL FAULTS.

 

#
Set-WmiNamespaceSecurity.ps1

# Example:
Set-WmiNamespaceSecurity root/cimv2 add steve Enable,RemoteAccess

 

Param (
[parameter(Mandatory=$true,Position=0)][string] $namespace,

   
[parameter(Mandatory=$true,Position=1)][string] $operation,

    [parameter(Mandatory=$true,Position=2)][string]
$account,

    [parameter(Position=3)][string[]]
$permissions = $null,

    [bool] $allowInherit = $false,

    [bool] $deny = $false,

    [string] $computerName = “.”,

   
[System.Management.Automation.PSCredential] $credential = $null)

   

Process {

    $ErrorActionPreference = “Stop”

 

    Function
Get-AccessMaskFromPermission($permissions) {

        $WBEM_ENABLE            = 1

                $WBEM_METHOD_EXECUTE = 2

                $WBEM_FULL_WRITE_REP   = 4

                $WBEM_PARTIAL_WRITE_REP              = 8

                $WBEM_WRITE_PROVIDER   = 0x10

                $WBEM_REMOTE_ACCESS    = 0x20

                $WBEM_RIGHT_SUBSCRIBE = 0x40

                $WBEM_RIGHT_PUBLISH      = 0x80

        $READ_CONTROL = 0x20000

        $WRITE_DAC = 0x40000

       

        $WBEM_RIGHTS_FLAGS =
$WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

           
$WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS =
“Enable”,”MethodExecute”,”FullWrite”,”PartialWrite”,`

           
“ProviderWrite”,”RemoteAccess”,”ReadSecurity”,”WriteSecurity”

 

        $permissionTable = @{}

 

        for ($i = 0; $i -lt
$WBEM_RIGHTS_FLAGS.Length; $i++) {

           
$permissionTable.Add($WBEM_RIGHTS_STRINGS[$i].ToLower(),
$WBEM_RIGHTS_FLAGS[$i])

        }

       

        $accessMask = 0

 

        foreach ($permission in $permissions)
{

            if (-not
$permissionTable.ContainsKey($permission.ToLower())) {

                throw “Unknown
permission: $permission`nValid permissions: $($permissionTable.Keys)”

            }

            $accessMask +=
$permissionTable[$permission.ToLower()]

        }

       

        $accessMask

    }

 

    if
($PSBoundParameters.ContainsKey(“Credential”)) {

        $remoteparams =
@{ComputerName=$computer;Credential=$credential}

    } else {

        $remoteparams = @{}

    }

       

    $invokeparams =
@{Namespace=$namespace;Path=”__systemsecurity=@”} + $remoteParams

 

    $output = Invoke-WmiMethod @invokeparams
-Name GetSecurityDescriptor

    if ($output.ReturnValue -ne 0) {

        throw “GetSecurityDescriptor
failed: $($output.ReturnValue)”

    }

 

    $acl = $output.Descriptor

    $OBJECT_INHERIT_ACE_FLAG = 0x1

    $CONTAINER_INHERIT_ACE_FLAG = 0x2

 

    $computerName = (Get-WmiObject
@remoteparams Win32_ComputerSystem).Name

   

    if ($account.Contains(‘\’)) {

        $domainaccount = $account.Split(‘\’)

        $domain = $domainaccount[0]

        if (($domain -eq “.”) -or
($domain -eq “BUILTIN”)) {

            $domain = $computerName

        }

        $accountname = $domainaccount[1]

    } elseif ($account.Contains(‘@’)) {

        $domainaccount = $account.Split(‘@’)

        $domain =
$domainaccount[1].Split(‘.’)[0]

        $accountname = $domainaccount[0]

    } else {

        $domain = $computerName

        $accountname = $account

    }

 

    $getparams =
@{Class=”Win32_Account”;Filter=”Domain=’$domain’ and
Name=’$accountname'”} + $remoteParams

 

    $win32account = Get-WmiObject @getparams

 

    if ($win32account -eq $null) {

        throw “Account was not found:
$account”

    }

 

    switch ($operation) {

        “add” {

            if ($permissions -eq $null) {

                throw “-Permissions must
be specified for an add operation”

            }

            $accessMask =
Get-AccessMaskFromPermission($permissions)

   

            $ace = (New-Object
System.Management.ManagementClass(“win32_Ace”)).CreateInstance()

            $ace.AccessMask = $accessMask

            if ($allowInherit) {

                $ace.AceFlags =
$OBJECT_INHERIT_ACE_FLAG + $CONTAINER_INHERIT_ACE_FLAG

            } else {

                $ace.AceFlags = 0

            }

                       

            $trustee = (New-Object
System.Management.ManagementClass(“win32_Trustee”)).CreateInstance()

            $trustee.SidString =
$win32account.Sid

            $ace.Trustee = $trustee

           

            $ACCESS_ALLOWED_ACE_TYPE = 0x0

            $ACCESS_DENIED_ACE_TYPE = 0x1

 

            if ($deny) {

                $ace.AceType = $ACCESS_DENIED_ACE_TYPE

            } else {

                $ace.AceType =
$ACCESS_ALLOWED_ACE_TYPE

            }

 

            $acl.DACL +=
$ace.psobject.immediateBaseObject

        }

       

        “delete” {

            if ($permissions -ne $null) {

                throw “Permissions cannot be specified
for a delete operation”

            }

       

           
[System.Management.ManagementBaseObject[]]$newDACL = @()

            foreach ($ace in $acl.DACL) {

                if ($ace.Trustee.SidString
-ne $win32account.Sid) {

                    $newDACL +=
$ace.psobject.immediateBaseObject

                }

            }

 

            $acl.DACL =
$newDACL.psobject.immediateBaseObject

        }

       

        default {

            throw “Unknown operation:
$operation`nAllowed operations: add delete”

        }

    }

 

    $setparams =
@{Name=”SetSecurityDescriptor”;ArgumentList=$acl.psobject.immediateBaseObject}
+ $invokeParams

 

    $output = Invoke-WmiMethod @setparams

    if ($output.ReturnValue -ne 0) {

        throw “SetSecurityDescriptor
failed: $($output.ReturnValue)”

    }

}

 

Setting security is a bit more complicated than retrieving
it.  You should have noticed that there
are many more parameters for this Powershell function.  Let’s go over each of them:

Param (
[parameter(Mandatory=$true,Position=0)][string] $namespace,

   
[parameter(Mandatory=$true,Position=1)][string] $operation,

   
[parameter(Mandatory=$true,Position=2)][string] $account,

    [parameter(Position=3)][string[]]
$permissions = $null,

    [bool] $allowInherit = $false,

    [bool] $deny = $false,

    [string] $computerName = “.”,

   
[System.Management.Automation.PSCredential] $credential = $null)

 

1.      
$namespace is the WMI namespace that you intend
to modify. 

2.      
$operation is either Add or Delete. 

a.      
If you want to modify existing Ace, you need to
Delete (which will delete all Aces for a user/group) and then Add back what you
want. 

3.      
$account is the name of the user/group

4.      
$permission is an array of the permissions to
grant to the user/group

5.      
$allowInherit controls whether child namespaces
will inherit the Ace

6.      
$deny indicates that this is a Deny Ace

a.      
By default, you are adding Allow Aces, but you
can explicitly Deny a permission

7.      
$computerName is optional and can be a remote
system

8.      
$credential is the credentials to the remote
system

 

Similar to the Get-WmiNamespaceSecurity script, we want to
handle the AccessMask in a friendly way. 
This is essentially the reverse of the function in the Get script and
uses a similar logic.  Based on the
supplied permissions, we produce the accessmask value for the Ace.

    Function
Get-AccessMaskFromPermission($permissions) {

        $WBEM_ENABLE            = 1

$WBEM_METHOD_EXECUTE = 2

                $WBEM_FULL_WRITE_REP   = 4

                $WBEM_PARTIAL_WRITE_REP              = 8

                $WBEM_WRITE_PROVIDER   = 0x10

                $WBEM_REMOTE_ACCESS    = 0x20

       $READ_CONTROL = 0x20000

        $WRITE_DAC = 0x40000

       

        $WBEM_RIGHTS_FLAGS =
$WBEM_ENABLE,$WBEM_METHOD_EXECUTE,$WBEM_FULL_WRITE_REP,`

           
$WBEM_PARTIAL_WRITE_REP,$WBEM_WRITE_PROVIDER,$WBEM_REMOTE_ACCESS,`

            $READ_CONTROL,$WRITE_DAC

        $WBEM_RIGHTS_STRINGS =
“Enable”,”MethodExecute”,”FullWrite”,”PartialWrite”,`

           
“ProviderWrite”,”RemoteAccess”,”ReadSecurity”,”WriteSecurity”

 

        $permissionTable = @{}

 

        for ($i = 0; $i -lt
$WBEM_RIGHTS_FLAGS.Length; $i++) {

           
$permissionTable.Add($WBEM_RIGHTS_STRINGS[$i].ToLower(),
$WBEM_RIGHTS_FLAGS[$i])

        }

       

        $accessMask = 0

 

        foreach ($permission in $permissions) {

            if (-not
$permissionTable.ContainsKey($permission.ToLower())) {

                throw “Unknown
permission: $permission`nValid permissions: $($permissionTable.Keys)”

            }

            $accessMask +=
$permissionTable[$permission.ToLower()]

        }

       

        $accessMask

    }

 

The next section takes advantage of Powershell 2.0 splatting
so I can store commonly used parameters in a single variable.  If credentials were not specified I don’t
want the credential prompt to come up since this script can be used locally or
remotely.  Finally, I get the current
Security Descriptor so I can add or delete from it.  Always check the ReturnValue returned from
calling the WMI method.

    if
($PSBoundParameters.ContainsKey(“Credential”)) {

        $remoteparams =
@{ComputerName=$computer;Credential=$credential}

    } else {

        $remoteparams = @{}

    }

       

    $invokeparams =
@{Namespace=$namespace;Path=”__systemsecurity=@”} + $remoteParams

 

    $output = Invoke-WmiMethod @invokeparams
-Name GetSecurityDescriptor

    if ($output.ReturnValue -ne 0) {

        throw “GetSecurityDescriptor
failed: $($output.ReturnValue)”

    }

 

    $acl = $output.Descriptor

 

Next, we need to retrieve the Win32_Account associated with
the user or group.  However, there are
different ways of specifying the account name. 
In a domain environment, you can use domain\account or
account@domain.  For a local account, you
can use .\account, computername\account, or just account.  This section of the script handles all these
cases and normalizes the result so we can get the resulting Win32_Account
instance.

    $computerName = (Get-WmiObject
@remoteparams Win32_ComputerSystem).Name

   

    if ($account.Contains(‘\’)) {

        $domainaccount = $account.Split(‘\’)

        $domain = $domainaccount[0]

        if (($domain -eq “.”) -or
($domain -eq “BUILTIN”)) {

            $domain = $computerName

        }

        $accountname = $domainaccount[1]

    } elseif ($account.Contains(‘@’)) {

        $domainaccount = $account.Split(‘@’)

        $domain =
$domainaccount[1].Split(‘.’)[0]

        $accountname = $domainaccount[0]

    } else {

        $domain = $computerName

        $accountname = $account

    }

 

    $getparams =
@{Class=”Win32_Account”;Filter=”Domain=’$domain’ and
Name=’$accountname'”} + $remoteParams

 

    $win32account = Get-WmiObject @getparams

 

    if ($win32account -eq $null) {

        throw “Account was not found:
$account”

    }

 

Now we move onto the actual operations.  Here we will handle adding a user and
granting/denying specific permissions. 
We create a new instance of the Win32_Ace class and set the inheritance
flags appropriately.  Next, we create a
new Win32_Trustee based on the user’s sid. 
This class is an embedded object in the Win32_Ace instance.  With the accessmask and acetype set, we
append it to the end of the existing DACL.

    switch ($operation) {

        “add” {

            if ($permissions -eq $null) {

                throw “-Permissions must be
specified for an add operation”

            }

            $accessMask =
Get-AccessMaskFromPermission($permissions)

   

            $ace = (New-Object
System.Management.ManagementClass(“win32_Ace”)).CreateInstance()

            $ace.AccessMask = $accessMask

            if ($allowInherit) {

                $ace.AceFlags =
$OBJECT_INHERIT_ACE_FLAG + $CONTAINER_INHERIT_ACE_FLAG

            } else {

                $ace.AceFlags = 0

            }

                       

            $trustee = (New-Object
System.Management.ManagementClass(“win32_Trustee”)).CreateInstance()

            $trustee.SidString =
$win32account.Sid

            $ace.Trustee = $trustee

           

            $ACCESS_ALLOWED_ACE_TYPE = 0x0

            $ACCESS_DENIED_ACE_TYPE = 0x1

 

            if ($deny) {

                $ace.AceType =
$ACCESS_DENIED_ACE_TYPE

            } else {

                $ace.AceType =
$ACCESS_ALLOWED_ACE_TYPE

            }

 

            $acl.DACL += $ace.psobject.immediateBaseObject

        }

 

For the delete operation, we search through all the Aces for
the specific user and remove them from the current DACL.  If you want to modify existing permission,
you would need to delete and then add. 
You could add a modify operation to the script, but the logic gets a bit
complicated if you want to support adding and removing permissions within the
same operation.

        “delete” {

            if ($permissions -ne $null) {

                throw “Permissions
cannot be specified for a delete operation”

            }

       

           
[System.Management.ManagementBaseObject[]]$newDACL = @()

            foreach ($ace in $acl.DACL) {

                if ($ace.Trustee.SidString
-ne $win32account.Sid) {

                    $newDACL +=
$ace.psobject.immediateBaseObject

                }

            }

 

            $acl.DACL =
$newDACL.psobject.immediateBaseObject

        }

       

        default {

            throw “Unknown operation:
$operation`nAllowed operations: add delete”

        }

    }

 

Now that we have the DACL modified, we just need to invoke
the SetSecurityDescriptor method on the namespace and check the ReturnValue to
ensure it succeded.

    $setparams =
@{Name=”SetSecurityDescriptor”;ArgumentList=$acl.psobject.immediateBaseObject}
+ $invokeParams

 

    $output = Invoke-WmiMethod @setparams

    if ($output.ReturnValue -ne 0) {

        throw “SetSecurityDescriptor
failed: $($output.ReturnValue)”

    }

 

Steve Lee

Senior Test Manager

Microsoft

Comments (29)

  1. phytan says:

    This is a great script, but I encounted a problem here. It seems the GetSecurityDescriptor

    in __SystemSecurity is not working. It always get RPC error here: $output = Invoke-WmiMethod @invokeparams -Name GetSecurityDescriptor

    But if I change GetSecurityDescriptor to GetSd or other method, it works. I don’t know if there is any preconditions for this WMI method GetSecurityDescriptor, could you give me some suggestions?

  2. What OS are you running this on?  GetSecurityDescriptor was added in Vista/Win2k8.

  3. phytan says:

    Thank you, problem solved. It is OS problem, and it works in powershell 2.0 RTM, not 2.0 CTP.

  4. EdwinCastro says:

    Does the winmgmt service need to be restarted for these types of changes to take effect?

  5. Changing namespace security does NOT require restarting the winmgmt service, however, I do believe it will only affect new connections and not existing ones.

  6. FlorianSiebert says:

    Hi Steve,

    many thanks for this script. It saves me a lot of time. Only one thing: to run it remotely I have to do the following changes:

    from:

       if ($PSBoundParameters.ContainsKey("Credential")) {

           $remoteparams = @{ComputerName=$computer;Credential=$credential}

       } else {

           $remoteparams = @{}

       }

    to:

       if ($PSBoundParameters.ContainsKey("Credential")) {

           $remoteparams = @{ComputerName=$computer;Credential=$credential}

       } else {

           $remoteparams = @{ComputerName=$computerName}

       }

    and from:

    $getparams = @{Class="Win32_Account";Filter="Domain='$domain' and Name='$accountname'"} + $remoteParams

    to:

    $getparams = @{Class="Win32_Account";Filter="Domain='$domain' and Name='$accountname'"}

    Kind regards,

    Florian

  7. Florian, you found a good bug in my code.  I believe the reason I original wrote the code the way I did was to handle two cases:

    1. local

    2. remote with explicit credentials

    What I didn't cover was "remote with implicit credentials".  So to support all 3, I should have checked if Credential was passed and if ComputerName was passed and handle those differently.  I appreciate knowing that you found this script useful.  Do let me know if there are other topics you'd like to have covered.

  8. oxxo says:

    Does it work on Windows Server 2003 R2?

    I get this error:

    Unable to find type [parameter(Mandatory=$true,Position=0)]: make sure that the assembly containing this type is loaded

    .

    At C:Documents and Settings60034XXXMy DocumentsWMIWmi.ps1:13 char:48

    + Param ( [parameter(Mandatory=$true,Position=0)][ <<<< string] $namespace,

    thanks

  9. oxxo says:

    The script works fine on Windows Server 2008, but I have a doubt

    How I can make for add an account to the namespace root and also applies to all subnamespaces?

    Thanks in advance

  10. @oxxo, regarding the Powershell error, make sure you are using Powershell 2.0

    For subnamespaces, if the ACL on the subnamespace is defined to inherit from the parent (and by default it is), then any modifications to the parent (including root) should be inherited automatically.  However, if a namespace ACL explicitly is set to not inherit, then you would need to update that subnamespace directly.

  11. Steve S says:

    I should add its Win2008R2

  12. ming says:

    by default ACL only applies to parent namespace. For this script to work on subnamespaces, you have to take out $OBJECT_INHERIT_ACE_FLAG = 0x1, tested on server 2008 and server 2008r2

  13. Calley says:

    I am trying to use this script and it won't work on Windows 2003, how do you get it to work?

  14. Are you getting an error message?  What parameters are you providing?

  15. Peter Nagl says:

    Hi, if i specified the parameter -allowInherit $true on the /root Object i received the error:

    Invoke-WmiMethod : Invalid parameter

    At C:UsersxxxxDesktopSet-WmiNamespaceSecurity.ps1:308 char:31

    +     $output = Invoke-WmiMethod <<<<  @setparams

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

    Without the allowInherit parameter everything worked.

    I set the permissions + inherit with the GUI and read the ACE. The ACEFlags was only set to 2

    In you Set-WmiNameSpaceSecurity.ps1 script you set both flags:

    $OBJECT_INHERIT_ACE_FLAG = 0x1

    $CONTAINER_INHERIT_ACE_FLAG = 0x2

    if ($allowInherit) {

                   $ace.AceFlags = $OBJECT_INHERIT_ACE_FLAG + $CONTAINER_INHERIT_ACE_FLAG

    changing that to:

    if ($allowInherit) {

                   $ace.AceFlags = $CONTAINER_INHERIT_ACE_FLAG

    fixed the error.

    Maybe this only happens if you want to set inherit on the root object?

    Anyway, my solution works now. Thank you for that GREAT script and all the insights.

    Peter

  16. Mat says:

    Steve – thanks a ton for providing this script. It's three years but did help me.

    Florian- thanks a ton for the implicit credentials.

  17. Gian says:

    Hi Steve:

    First, thanks a lot for this script.

    Steve, not sure why when it is trying to modify the permissions in rootcimv2 I got the message below. But for root or rootdefault it is working fine.

    Thanks.

    Invoke-WmiMethod : Unexpected error

    At C:installSQLPowershellscriptsSet-WmiNamespaceSecurity.ps1:125 char:31

    +     $output = Invoke-WmiMethod <<<<  @invokeparams -Name GetSecurityDescriptor

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

  18. rakesh kumar says:

    hi am getting this error, do i need to change something in it before pasting it to PS?

    Invoke-WmiMethod : Invalid namespace

    At line:38 char:31

    +     $output = Invoke-WmiMethod <<<<  @invokeparams -Name GetSecurityDescriptor

       + CategoryInfo          : InvalidOperation: (:) [Invoke-WmiMethod], ManagementException

       + FullyQualifiedErrorId : InvokeWMIManagementException,Microsoft.PowerShell.Commands.InvokeWmiMethod

  19. Nate says:

    Great scripts, Steve, they seem to be working great for me.

    One question: How would one modify the security to add a user for the "main" wmi schema as seen in the GUI for winrm configsddl wmi? What resource_uri does this map to, or how would I go about finding out?

  20. Currently working on a DSC resource to replace the need for this script.  No ETA, but thinking June is realistic.  This should address any script errors people have been getting (I hope).

    @Nate, if you execute 'winrm help aliases' you'll see what resource uri 'wmi' maps to (along with others)

  21. Nate says:

    Thanks, Steve! This should get me well on my way to completing my powershell script!

    (working on getting event logging automagically set up for RSA Security Analytics)

  22. Check out the DSC resource that should replace the need for these scripts: blogs.msdn.com/…/use-dsc-to-manage-wmi-namespace-security.aspx

  23. Chris AZ says:

    I have 2 domains, A and B. they have a trust between each other, different forests. my account is in domain A. I want to set wmi security on domain B, but i get Account Not Found error on this.

    Account was not found: chris@domainA.local

    At C:toolswinrmSet-WMInamespaceSecurity2.ps1:95 char:9

    +         throw "Account was not found: $account"

    +         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

       + CategoryInfo          : OperationStopped: (Account was not…domainA.local:String) [], RuntimeException

       + FullyQualifiedErrorId : Account was not found: chris@domainA.local

    I also tried it with domainAchris and same result. If I set the wmi security on rootcimv2 manually, it can find the account.

    any thoughts on this?

  24. I'm guessing that the Domain value isn't matching.  Try a local WMI query on the target box for that account using win32_account and see what it shows for the Domain and Name properties.  Then see if you can execute that same query remotely using domainA credentials.

  25. Darren P says:

    Hey Steve,

    I am trying to run the WMI script you have above but am getting an error that confuses me.

    I am also getting the "throw "Account was not found: $account"", our naming convention is first.last, I have also tried with an account that has no special characters.

    I did as you suggested and ran win32_account on the remote device and it lists the domain accounts including the one I am using to test.

    I also tried running domainfirst.last and it doesn't generate an error, I then try to run "Get-WmiObject -Class Win32_PerfRawData_PerfDisk_LogicalDisk -ComputerName $ComputerName"

    and it provides me with an access denied error.

    To troubleshoot this further I gave the account local admin access and the same command ran successfullly remotely and locally.

    Do you have any suggestions on how to resolve this?

  26. My suggestion would be to run the script in ISE and set a breakpoint to where it's throwing an error.  Then try manually running the next line which may give you more details on why it's failing.  Unfortunately, it's going to be impossible for me to troubleshoot this.  I would also recommend trying out the new DSC resource I published above.  One of the big differences is that the DSC resource is executed locally (you can push it remotely) so some of the issues of this old script running remotely should go away.

  27. Darren P says:

    Hi Steve,

    Thanks for taking the time to reply.

    We're far from ready to actually support DSC sadly, I will see if I can sort out where the script is getting stuck.

  28. For anyone still trying to use this script remotely, the problem is with this line:

    $win32account = Get-WmiObject @getparams

    when you run the script remotely, this line is still querying the local machine for the account.  this works if both machine are in the same domain and you are using domain accounts/groups since it can find it.  the fix is to change this line to:

    if ($computername -ne ".") {

     $win32account = Get-WmiObject @getparams -computername $computername -credential $credential

    } else {

     $win32account = Get-WmiObject @getparams

    }

    This will make the query run remotely on the same target box and will work.  Note that I have to separate local and remote out even though Get-WmiObject will happily take "." as local machine, it'll complain if a credential is passed that isn't $null (current user).

  29. Looks like there was a reason I wrote the code as I did originally which I should have added a comment to explain.  If you make the changes I wrote above, you'll actually break other scenarios.  The problem has to do with doing a double network hop.  Assume you are on ClientA and remotely managing TargetB.  In the original script, I was resolving accounts locally on ClientA.  This works perfectly fine if both machines are in a workgroup and have the same local accounts or if both machines are in the same domain.  If ClientA and TargetB are in different domains or if one is in a workgroup or if they are both in a workgroup and have different local accounts it doesn't work.  With the change, the account gets resolved remotely on TargetB.  However, if this is a domain account, it will fail with Access Denied as it will incur a second network hop to the domain controller to resolve the account.

    A proper fix here would be somewhat complicated to satisfy all scenarios.  Easiest would be a switch to indicate if the account should be resolved locally or remotely, but this still doesn't work if ClientA is not in the same domain as TargetB.  To make that work, you would need to use Kerberos delegation or change to using the new CIM cmdlets and CredSSP.