Categories

18186

CIM or WMI – – accessing remote machines

I much prefer the CIM cmdlets for accessing remote machines. The WMI cmdlets use DCOM which is firewall unfriendly and can often be unavailable of a server – cue the dreaded RPC server is unavailable error messages.

By contrast the CIM cmdlets use WSMAN.

For one off access to a remote machine use the computername parameter

Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName RSSURFACEPRO2

 

If you want to access a machine multiple times in the session create a CIM session – analagous to a remoting session

 

$cs = New-CimSession -ComputerName RSSURFACEPRO2
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $cs

 

By default a CIM session uses WSMAN

£> $cs


Id           : 1
Name         : CimSession1
InstanceId   : 30c2b530-4ff7-448e-b68d-1f1282890e6a
ComputerName : RSSURFACEPRO2
Protocol     : WSMAN

 

though you can configure them to use DCOM if need be

$opt = New-CimSessionOption -Protocol DCOM
$csd = New-CimSession -ComputerName RSSURFACEPRO2 -SessionOption $opt
Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $csd

 

When would you need to use DCOM – if you are accessing a machine with PowerShell 2 installed. The CIM cmdlets want to use WSMAN 3 and will error if you access a machine with WSMAN 2 installed however if you include a –Filter they will work

So

Get-CimInstance -ClassName Win32_OperatingSystem -ComputerName computer1

will fail if computer1 is running WSMAN 2 (PowerShell 2)

However, if you change the command to include a filter

Get-CimInstance -ClassName Win32_OperatingSystem -Filter "Manufacturer LIKE 'Microsoft%'" -ComputerName computer1

 

Even if, as in this case, the filter doesn’t actually do anything

Share Permissions – setting deny

The last change to the share permissions functions to modify the Set-SharePermissions functions to enable the application of Deny permissions.

The function becomes:

#requires -Version 3.0
function Set-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME,

  [parameter(ParameterSetName="AllowPerm")]
  [switch]$allow,

  [parameter(ParameterSetName="DenyPerm")]
  [switch]$deny
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}


$tclass = [wmiclass]"\\$computername\root\cimv2:Win32_Trustee"
$trustee = $tclass.CreateInstance()
$trustee.Domain = $domain
$trustee.Name = $trusteeName

$aclass = [wmiclass]"\\$computername\root\cimv2:Win32_ACE"
$ace = $aclass.CreateInstance()
$ace.AccessMask = $accessmask

switch ($psCmdlet.ParameterSetName) {
"AllowPerm"  {$ace.AceType = 0}
"DenyPerm"  {$ace.AceType = 1}
default {Write-Host "Error!!! Should not be here" }
}

$ace.Trustee = $trustee

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){

if (($oace.Trustee.Name -eq $trusteeName) -AND ($oace.Trustee.Domain -eq $domain) ) {
    continue
}
else
{
    $newsd.DACL += $oace
}
}
$newsd.DACL += $ace

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$share.SetSecurityDescriptor($newsd)

} # end function

The changes are to add two switches –allow & –deny.  Put them in different parametersets to ensure mutual exclusivity.

As you are using parametersets you can use a switch based on the parameterset name to set the ACE type.

switch ($psCmdlet.ParameterSetName) {
"AllowPerm"  {$ace.AceType = 0}
"DenyPerm"  {$ace.AceType = 1}
default {Write-Host "Error!!! Should not be here" }
}

Everything else remains the same.

Share Permissions – adding a Deny permission

Modifying the Add-SharePermission function to enable the application of Deny permissions is a simple matter of adding a switch parameter –deny  and modifying the way the AcreType is set:

#requires -Version 3.0
function Add-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME,

  [switch]$deny
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}


$tclass = [wmiclass]"\\$computername\root\cimv2:Win32_Trustee"
$trustee = $tclass.CreateInstance()
$trustee.Domain = $domain
$trustee.Name = $trusteeName

$aclass = [wmiclass]"\\$computername\root\cimv2:Win32_ACE"
$ace = $aclass.CreateInstance()
$ace.AccessMask = $accessmask
$ace.AceFlags = 0

if ($deny)
{
  $ace.AceType = 1
}
else
{
  $ace.AceType = 0
}

$ace.Trustee = $trustee

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){$newsd.DACL += $oace}
$newsd.DACL += $ace

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$share.SetSecurityDescriptor($newsd)

} # end function

The hard work is done by this part of the code:

if ($deny)
{
  $ace.AceType = 1
}
else
{
  $ace.AceType = 0
}

 

where the value of AceType is set to 1 for deny and 0 for allow.

Share Permissions – working with Deny

Permissions can be set to either allow access ot to deny access.  The functions I’ve presented so far only work with Allow permissions. Using Deny permissions should be avoided if at all possible but sometimes there’s no alternative.

First thing is to modify Get-SharePermission so that it shows if the permission is allowed or denied.

#requires -Version 3.0
function Get-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$computername = $env:COMPUTERNAME
)

$shss = Get-CimInstance -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-CimMethod -InputObject $shss -MethodName GetSecurityDescriptor |
select -ExpandProperty Descriptor

foreach ($ace in $sd.DACL) {

switch ($ace.AccessMask) {
   1179817 {$permission = 'Read'}
   1245631 {$permission = 'Change'}
   2032127 {$permission = 'FullControl'}
   default {$permission = 'Special'}
}
 
$trustee = $ace.Trustee
$user = "$($trustee.Domain)\$($trustee.Name)"

switch ($ace.AceType) {
   0 {$status = 'Allow'}
   1 {$status = 'Deny'}
   2 {$status = 'Audit'}
}

$props = [ordered]@{
   User = $user
   Permissions = $permission
   Status = $status
}
New-Object -TypeName PSObject -Property $props
} # emd foreach
} # end function

The modification is to add a switch based on the AceType property of the Win32_ACE object (http://msdn.microsoft.com/en-us/library/aa394063(v=vs.85).aspx) to determine the status of the permission. Add a property called Status to the output object and set it equal to the status determined in the switch.

Share Permissions – changing

So far you’ve seen how to read, remove and add permissions to a share.  The final scenario to be covered is modifying a permission.

The functions I’ve presented to date only enable you to set Allow permissions on a share. I’ll be covering Deny permissions in later posts. This mimics the way that I tend to develop functionality – get part working then add more in increments until you have what you need.

For now this is function will enable you to modify permissions on a share:

#requires -Version 3.0
function Set-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}


$tclass = [wmiclass]"\\$computername\root\cimv2:Win32_Trustee"
$trustee = $tclass.CreateInstance()
$trustee.Domain = $domain
$trustee.Name = $trusteeName

$aclass = [wmiclass]"\\$computername\root\cimv2:Win32_ACE"
$ace = $aclass.CreateInstance()
$ace.AccessMask = $accessmask
$ace.AceFlags = 0
$ace.AceType = 0
$ace.Trustee = $trustee

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){

if (($oace.Trustee.Name -eq $trusteeName) -AND ($oace.Trustee.Domain -eq $domain) ) {
    continue
}
else
{
    $newsd.DACL += $oace
}
}
$newsd.DACL += $ace

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$share.SetSecurityDescriptor($newsd)

} # end function

If you compare this to the Add-SharePermission and Remove-SharePermission you’ll see that the Set-SharePermission is really the Add-SharePermission function with the code to add the old ACE to the new DACL modified to be similar to Remove-SharePermission.

if (($oace.Trustee.Name -eq $trusteeName) -AND ($oace.Trustee.Domain -eq $domain) ) {
    continue
}
else
{
    $newsd.DACL += $oace
}

The code loops through the old ACEs and as long as the name AND domain don’t match the trustee for whom you’re changing permissions they’re added to the new DACL.  In other worlds the old permissions for the trustee are removed from the DACL.

Share Permissions – Removing

You’ve seen how to read share permissions and how to add share permissions – now its time to remove share permissions.  Most of the code we need is in the Add-Sharepermission function – it just needs a bit of a tweak.

#requires -Version 3.0
function Remove-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}

 

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){

if (($oace.Trustee.Name -eq $trusteeName) -AND ($oace.Trustee.Domain -eq $domain) -AND ($oace.Accessmask -eq $accessmask)) {
    continue
}
else
{
    $newsd.DACL += $oace
}
}

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'"
$share.SetSecurityDescriptor($newsd)

} # end function

The function uses the same parameters as Add-Permission i.e. mandatory share name, trustee name and permission with optional computer and domain names. The switch statement converts the permission into an access mask.

Use Get-WmiObject to  get the current security descriptor and use [wmiclass] to create a new one.

Copy the control flags and the ACE except for the any that correspond to the trustee name, domain and the permission you want to remove.

Use SetSecurityDescriptor to apply the new permissions

Share Permissions – adding

Having seen how to read the permissions on a share its time to turn to one of the other common tasks associated with shares – adding permissions.  This is usually done when the share is created but there are scenarios where you need to add extra permissions.

I’ve written the function Add-SharePermission to accomplish this task:

#requires -Version 3.0
function Add-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}


$tclass = [wmiclass]"\\$computername\root\cimv2:Win32_Trustee"
$trustee = $tclass.CreateInstance()
$trustee.Domain = $domain
$trustee.Name = $trusteeName

$aclass = [wmiclass]"\\$computername\root\cimv2:Win32_ACE"
$ace = $aclass.CreateInstance()
$ace.AccessMask = $accessmask
$ace.AceFlags = 0
$ace.AceType = 0
$ace.Trustee = $trustee

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){$newsd.DACL += $oace}
$newsd.DACL += $ace

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'"
$share.SetSecurityDescriptor($newsd)

} # end function

The function starts with a requires restricting the function to version 3 and above.  The function would run on version 2 so this is for consistency with the Get-SharePermission function I showed last time.

The parameter block includes a mandatory sharename and trusteename (user or group). Optional parameters allow a domain and computername to be set – both default to the local machine. A final mandatory parameter allows the specification of the permissions to be applied – read, change or full control – these values are validated at input using the ValidateSet option.

The next step in the function is to take the permission and turn it into an access mask using a switch – its the inverse of the one you saw in Get-SharePermission.

I’ve had to use the [wmiclass] type accelerator to to create the Win32_Trustee and Win32_ACE instances I need.  The CIM cmdlets can’t be used because these classes don’t have keys defined.

The properties are set on the 2 objects.

Get-WmiObject is used to get the SecurityDescriptor of the share.

A new security descriptor is created with the relevant properties from the existing one copied across – the current ACEs have to be copied individually as shown. The new ACE is added to the DACL

Finally Get-WmiObject is used to retrieve the share object and the SetSEcurityDescriptor method is used to apply the new security descriptor.

Some interesting uses the wmiclass accelerator to make this work but the function shows it is doable without too much effort.

Share Permissions – getting

I’ve written about working with share permissions a couple of times but a post on the forum (powershell.org) got me thinking about it again.  This time I’m going to use the CIM cmdlets rather than the WMI cmdlets I’ve used in the past.

My test machine has a test share called Test2April so that’s what we’ll work with. The first job is to understand the permissions assigned to the share.  There are 3 possibilities for share permissions:

  • Read
  • Change
  • Full control

I assigned these to distinct users – Everyone, ChangeUser and Fulluser respectively.

Discovering the permissions can be performed using this function:

#requires -Version 3.0
function Get-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$computername = $env:COMPUTERNAME
)

$shss = Get-CimInstance -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-CimMethod -InputObject $shss -MethodName GetSecurityDescriptor |
select -ExpandProperty Descriptor

foreach ($ace in $sd.DACL) {

switch ($ace.AccessMask) {
   1179817 {$permission = 'Read'}
   1245631 {$permission = 'Change'}
   2032127 {$permission = 'FullControl'}
   default {$permission = 'Special'}
}
 
$trustee = $ace.Trustee
$user = "$($trustee.Domain)\$($trustee.Name)"

$props = [ordered]@{
   User = $user
   Permissions = $permission
}
New-Object -TypeName PSObject -Property $props
} # emd foreach
} # end function

The function takes a mandatory parameter of the share name with an option parameter of computername that defaults to the local machine.

Use the Win32_LogicalShareSecuritySetting class to get the security information. The security descriptor is retrieved using its GetSecurityDescriptor method. The security descriptor stores the DACL for the share.

Each ACE in the DACL is interrogated to determine its access mask and the trustee associated with that permission. I’ve given the access mask for the 3 common permissions (Read, Change, Full Control) – anything else is listed as special.  You can use the techniques in technique 51 form PowerShell and WMI or download my PAM module from codeplex (http://psam.codeplex.com/) and use Get-ShareAccessMask.

The domain and name of the trustee is put into the $user variable – it could just as easily be a group that comes through.

Create an ordered hash table with the results and output as an object.

The output will look something like this:

£> Get-SharePermission -sharename Test2April | ft -AutoSize

User                     Permissions
----                     -----------
RSsurfacePro2\ChangeUser Change    
\Everyone                Read      
RSsurfacePro2\FullUser   FullControl

Provider is not capable of the attempted operation

A question on the (powershell.org) forum described a situation where the user was trying to use Set-WmiInstance to set a property on a particular WMI class instance.

The attempt failed and the message “Provider is not capable of the attempted operation” was part of the reported exception.

This message very often indicates that you are trying to write to a readonly property – which was the case here.  The class was Win32_SystemEnclosure and the property was SMBIOSAssetTag.

If you use get-member

Get-WmiObject -Class Win32_SystemEnclosure | Get-Member

and look at the SMBIOSAssetTag property you’ll see this

SMBIOSAssetTag            Property      string SMBIOSAssetTag {get;set;}

BUT that’s not right because if you look at the documentation at http://msdn.microsoft.com/en-us/library/aa394474%28v=vs.85%29.aspx

you’ll see this:

SMBIOSAssetTag
Data type: string
Access type: Read-only

Asset tag number of the system enclosure.

Get-Member doesn’t report correctly on whether a WMI class’ property is readonly.  The best way to find out is to use Get-CimClass

£> $class = Get-CimClass Win32_SystemEnclosure
£> $class.CimClassProperties['SMBIOSAssetTag']


Name               : SMBIOSAssetTag
Value              :
CimType            : String
Flags              : Property, ReadOnly, NullValue
Qualifiers         : {MappingStrings, read}
ReferenceClassName :

And you can see in the flags that the property is ReadOnly.

Get-CimClass should be your starting point when investigating WMI classes.

WMI against remote machines

WMI is a great tool for managing your Windows machines – I’d argue that PowerShell wouldn’t be as powerful as it is without WMI. If you question that remember that 60% of the additional cmdlets in Windows Server 2012 & 2012 R2 are CDXML based i.e. publish a WMI class as  a PowerShell module.

PowerShell 2.0 introduced a suite of WMI cmdlets:

Get-WmiObject
Invoke-WmiMethod
Register-WmiEvent
Remove-WmiObject
Set-WmiInstance

PowerShell 3.0 introduced the CIM cmdlets:

Get-CimAssociatedInstance
Get-CimClass
Get-CimInstance
Get-CimSession
Invoke-CimMethod
New-CimInstance
New-CimSession
New-CimSessionOption
Register-CimIndicationEvent
Remove-CimInstance
Remove-CimSession
Set-CimInstance

So which should you use?

There are a number of differences.

The WMI cmdlets return live objects and the CIM cmdlets return inert objects. This isn’t too much of an issue if you use Invoke-CimMethod.  I’d also recommend using Invoke-WMImethod over creating an object and calling the method on that.

The real difference is in the protocol used to access remote machines. The WMI cmdlets use DCOM and the CIM cmdlets default to WSMAN. At this point you may be thinking that you can just use the CIM cmdlets but the remote machine must be running WSMAN 3.0 which comes with PowerShell 3.0 or 4.0..  The CIM cmdlets can’t connect to WSMAN 2.0 which is the PowerShell 2.0 version.

At that point you have to use a CIM session that drops back to DCOM or run Get-WMIobject through a PowerShell remoting session.

Overall the CIM cmdlets win – especially when you consider Get-CimClass and Get-CimAssociatedInstance