Monthly Archives: May 2012

Few PowerShell Functions Around Windows Security

Written by Tao Yang

As parts of the PowerShell project that I’m currently working on, with the help with other people’s contribution in various forums and blogs, I have produced few PowerShell functions around Windows security:

Validate Credential

function Validate-Credential($Cred)
{
$UserName = $Cred.Username
$Password = $Cred.GetNetworkCredential().Password
Add-Type -assemblyname System.DirectoryServices.AccountManagement
$DS = New-Object System.DirectoryServices.AccountManagement.PrincipalContext([System.DirectoryServices.AccountManagement.ContextType]::Machine)
Try {
$ValidCredential = $DS.ValidateCredentials($UserName, $Password)
} Catch {
#if the account does not have required logon rights to the local machine, validation failed.
$ValidCredential = $false
}
Return $ValidCredential
}

Usage:

$MyCredential = Get-Credential

$ValidCredential = Validate-Credential $MyCredential

Get Current User Name

function Get-CurrentUser
{
$me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
Return $me
}

Usage:

$me = Get-CurrentUser

Check If Current User has Local Admin Rights

function AmI-LocalAdmin
{
return ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
}

Usage:

$IAmAdmin = AmI-LocalAdmin

$IAmAdmin

Check if a user is a member of a group

function Check-GroupMembership ([System.Security.Principal.WindowsIdentity]$User, [string]$GroupName)
{
$WindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($User)

if($WindowsPrincipal.IsInRole($GroupName))
{
$bIsMember = $true
} else {
$bIsMember = $false
}
return $bIsMember
}

Usage:

#Current User:

$me = [System.Security.Principal.WindowsIdentity]::GetCurrent()

$group = “\domain admins”

$IsMember = Check-GroupMembership $me $group

#Another User (Using User Principal Name @):

$user = new-object system.security.principal.windowsidentity("tyang@corp.tyang.org")

$group = “\domain admins”

$IsMember = Check-GroupMembership $user $group

Get Local Machine’s SID

function Get-LocalMachineSID
{
$LocalAdmin = Get-WmiObject -query "SELECT * FROM Win32_UserAccount WHERE domain='$env:computername' AND SID LIKE '%-500'"
$MachineSID = $localAdmin.SID.TrimEnd("-500")
Return $MachineSID
}

Usage:

$LocalMachineSID = Get-LocalMachineSID

Check If an account is a domain account (as opposed to local account)

Note: This function also requires the Get-LocalMachineSID function listed above

Function Is-DomainAccount ([System.Security.Principal.WindowsIdentity]$User)
{
$LocalMachineSID = Get-LocalMachineSID
if ($User.user.value -ine $LocalMachineSID)
{
$bIsDomainAccount = $true
} else {
$bIsDomainAccount = $false
}
$bIsDomainAccount
}

Usage:

#Current User:

$me = [System.Security.Principal.WindowsIdentity]::GetCurrent()

$IsDomainAccount = Is-DomainAccount $me

#Another User (Using User Principal Name @):

$user = new-object system.security.principal.windowsidentity(<a href="mailto:tyang@corp.tyang.org">tyang@corp.tyang.org</a>)

$IsDomainAccount = Is-DomainAccount $user

Powershell: Prevent Users To View and Change Input or Config Files That Are Used by a Script

Written by Tao Yang

Often, I use .xml or .ini files to store settings that a PowerShell script uses. When I distribute my scripts to end users, sometimes, I want to make sure users cannot manually view or change the content of these config files.

Below is what I did to achieve the goal:

  1. Create a password protected zip file that contains the config file (.xml or .ini).
  2. rename the zip file from xxxxxx.zip to xxxxxx.bin
  3. In powershell script, use ICSharpCode.SharpZipLib.dll to unzip renamed zip file
  4. compile powershell script to exe so users cannot view the script to figure out the zip file password.
  5. read the content of the extracted config file
  6. delete extracted config file

To compile the powershell script, I can use one of these tools:

Below is a sample Powershell script (Zip-Test.PS1) I have written to read a xml file inside a renamed zip file:

param ([string]$FilePath)
$ziplib = Join-Path $FilePath "ICSharpCode.SharpZipLib.dll"
[System.Reflection.Assembly]::LoadFrom("$ziplib") | Out-Null
$ZipName = "Health-Check.bin"
$XmlName = "Health-Check.xml"
$xmlPath = Join-Path $FilePath $XmlName
$ZipPath = Join-Path $FilePath $ZipName
$objZip = New-Object ICSharpCode.SharpZipLib.Zip.FastZip
$objZip.Password = "password"
$objzip.ExtractZip($ZipPath, $FilePath, $XmlName)
if ((Test-Path $xmlPath))
{
$xml = (get-content $xmlPath)
Remove-Item $xmlPath -Force
}
$xml.configuration

The script extracts and reads the health-check.xml file and deletes health-check.xml straightaway, it happens so fast, it won’t be possible for end users to access the file. Below is the output from above sample code (content of my XML file):

image

One thing to keep in mind: in most of my scripts, I use

$thisScript = Split-Path $myInvocation.MyCommand.Path -Leaf
$scriptRoot = Split-Path (Resolve-Path $myInvocation.MyCommand.Path)

To determine the script name and location. $MyInvocation does not work anymore after I converted the Powershell script to EXE. Therefore, from my above example, I’m actually passing the directory location into the script as a parameter.

Using SCOM PowerShell Snap-in and SDK client with a PowerShell Remote Session

Written by Tao Yang

Recently, I’ve been working on a utility based on PowerShell scripts using WinForms GUI to perform some SCOM tasks (i.e. create maintenance window, approve manually installed agents, adding network devices, etc.). Since this script is going to be widely used in the organisation when it’s completed, I’ve always kept in mind that when users run this utility, the utility should only connect to SCOM SDK service when required and disconnect as soon as the task is done. In another word, I don’t want this utility to remain connected to the SDK service because Microsoft recommends the concurrent connections should not exceed 50 per management group.

So I did some testing to make sure my scripts disconnects from the RMS SDK service. I opened perfmon on RMS watching the “Client Connections” counter under OpsMgr SDK Service:

image

image

and want to make sure the performance counter drops when the script is supposed to disconnect from SCOM management group. In my script, I use both the SCOM PowerShell Snap-in and the SCOM SDK, below is what the code looks like:

SCOM PowerShell Snap-in:

Connect to management group:

$RMS = "<RMS Server Name>"

Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client
New-PSDrive -Name:Monitoring -PSProvider:OperationsManagerMonitoring -Root:\
Set-Location "OperationsManagerMonitoring::"
new-managementGroupConnection -ConnectionString:$RMS | Out-Null
Set-Location $RMS

Disconnect from management group:

$CurrentMG = get-managementGroupConnection
if ($CurrentMG -ne $null)
{
$CurrentMG | Remove-ManagementGroupConnection | Out-Null
}

SCOM SDK:

Firstly, Load Assembly:

[System.Reflection.Assembly]::LoadFrom("$sdkDir\Microsoft.EnterpriseManagement.OperationsManager.Common.dll") | Out-Null
[System.Reflection.Assembly]::LoadFrom("$sdkDir\Microsoft.EnterpriseManagement.OperationsManager.dll") | Out-Null

Connect to management group:

$UserName = "<user name>"

$UserDomain = "<user domain>"

$password = "<password>"

$securePassword = ConvertTo-SecureString $password –AsPlainText -Force

$MGConnSetting = New-Object Microsoft.EnterpriseManagement.ManagementGroupConnectionSettings($RootMS)
$MGConnSetting.UserName = $UserName
$MGConnSetting.Domain = $UserDomain
$MGConnSetting.Password = $SecurePassword
$ManagementGroup = New-Object Microsoft.EnterpriseManagement.ManagementGroup($MGConnSetting)

Disconnect from management group:

I couldn’t find a “disconnect” method for the Microsoft.EnterpriseManagement.ManagementGroup object. So I tried to simply remove the variable:

Remove-Variable ManagementGroup

I couldn’t unload the SDK DLLs as I read it’s a limitation in .NET, the only way to unload a loaded DLL is to close the app.

Test Results:

Regardless which way I use to connect to SCOM (PowerShell Snap-in or SDK), the perf counter does not drop when I tried to disconnect using methods above. In fact, I could only get the counter drop when I close the Powershell console (or exit my GUI app which is just a pure Powershell script).

image

As shown above, notice that as soon as I exit PowerShell, the counter has dropped by 1.

Therefore, I thought I had 2 options to work around this issue.

1. Getting the script to launch another powershell.exe instance when trying to connect to SCOM every time but by doing so, I can’t really pass data / variable back to my script.

2. Use PowerShell Remoting to create a PS Session on local computer, run whatever needs to run against SCOM and remove the PS Session when it’s done. By doing so, I can still pass variables back to my script.

So I’ve decided to go with PowerShell Remoting. I’ve used “Enable-PSremoting –force” cmdlet to enable PS Remoting with all default settings.

I’ll use a simple get-agent cmdlet via PS Remoting as example, I’ve written something like this:

$RMS = "<RMS Server Name>"
$AgentName = "<Agent Computer Name>"
$NewSession = new-pssession
$agent = invoke-command  -session $NewSession -ScriptBlock {
param($RMS,$AgentName)

Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client
New-PSDrive -Name:Monitoring -PSProvider:OperationsManagerMonitoring -Root:\
Set-Location "OperationsManagerMonitoring::"
new-managementGroupConnection -ConnectionString:$RMS | Out-Null
Set-Location $RMS
$Agent = Get-Agent | Where-Object {$_.PrincipalName -imatch $AgentName}
$Agent
} -ArgumentList $RMS, $AgentName
Remove-PSSession $NewSession

I ran above code using an account that is a domain admin in my test environment and it’s also a SCOM administrator in my management group. But somehow I get this error:

The user does not have sufficient permission to perform the operation.

image

After some research, I realised that I have to use the CredSSP (Credential Security Support Provider) authentication to pass my credential from the local Powershell session to the PS Remoting session (in this case, also on my local machine). So I modified my script to use Credssp when creating the new PS Session:

$me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

$NewSession = new-pssession -ComputerName $env:COMPUTERNAME -Authentication Credssp -Credential (Get-Credential $me)

It turned out, after the modification, the code still would not work:

image

I then found that I will also have to configure the remote session to pass my credential to the remote server again – in this case, the SDK service in SCOM RMS (second hop). so my credential will be passed from Local PowerShell session –> PS remote session on the local computer –> SCOM RMS SDK Service.

In addition to “Enable-PSRemoting –force”, I had perform the following to make it work:

1. Enable WinRM CredSSP to allow the second hop:

Via PowerShell:
  • Set-Item WSMAN:\localhost\client\auth\credssp –value $true
  • Set-Item WSMAN:\localhost\service\auth\credssp –value $true
Or Via Group Policy:
  • Computer Configuration\Administrative Templates\Windows Components\Windows Remote Management (WinRM)\WinRM Client\Allow CredSSP authentication – Set to “Enabled”
  • Computer Configuration\Administrative Templates\Windows Components\Windows Remote Management (WinRM)\WinRM Service\Allow CredSSP authentication – Set to “Enabled”

2. Configure Credentials Delagations

in Group Policy (either domain GPO or local policy), under

Computer Configuration\Administrative Templtes\System\Credential Delegation\Allow Delegating Fresh Credentials
– Set to Enabled

– Add “WSMAN/<local computer name>” to the server list

image

Now, after I updated the group policy (gpupdate /force), the code should just work. As shown below, I have retrieved the agent information using SCOM Powershell Snap-in via a PS remote session.

image

And now if I take a look at the OpsMgr SDK Service “Client Connections” perf counter:

image

My script has connected to the SDK service for few seconds then disconnected!

Conclusion:

My code ended up like this:

$me = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name

$RMS =  "<RMS Server Name>"
$AgentName = "<Agent Computer Name>"
$NewSession = new-pssession -ComputerName $env:COMPUTERNAME -Authentication Credssp -Credential (Get-Credential $me)
$agent = invoke-command  -session $NewSession -ScriptBlock {
param($RMS,$AgentName)

Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client
New-PSDrive -Name:Monitoring -PSProvider:OperationsManagerMonitoring -Root:\
Set-Location "OperationsManagerMonitoring::"
new-managementGroupConnection -ConnectionString:$RMS | Out-Null
Set-Location $RMS
$Agent = Get-Agent | Where-Object {$_.PrincipalName -imatch $AgentName}
$Agent
} -ArgumentList $RMS, $AgentName
Remove-PSSession $NewSession

I could not use “localhost” as computer name when creating new PS session (and adding “WSMAN/localhost” in “Allow Delegating Fresh Credentials policy”. It doesn’t work.

More Reading:

On OpsMgr SDK service client connections counter:

http://blogs.technet.com/b/kevinholman/archive/2008/10/27/how-many-consoles-are-connected-to-my-rms.aspx

http://thoughtsonopsmgr.blogspot.com.au/2010/12/how-to-get-alert-when-too-many-scom.html

On CredSSP , PS Remoting and SCOM PowerShell Cmdlets:

http://blogs.msdn.com/b/powershell/archive/2008/06/05/credssp-for-second-hop-remoting-part-i-domain-account.aspx

http://blogs.technet.com/b/stefan_stranger/archive/2010/11/02/using-powershell-remoting-to-connect-to-opsmgr-root-management-server-and-use-the-opsmgr-cmdlets.aspx

Additionally, I ran into this free ebook about a week ago, Even though I’m still reading it, it’s a pretty good book: Secrets of PowerShell Remoting.

My Observation on SCCM Clients BITS Settings

Written by Tao Yang

Yesterday, while we were reviewing the SCCM (2007 R3) client BITS settings at work, we (my team) have some interesting findings with SCCM client’s BITS settings.

We found when the BITS bandwidth throttling settings are configured for a SCCM primary site. SCCM clients get the policy and write the settings into Windows local policy:

SCCM Computer Client Agent BITS Settings:

image

BITS Settings from SCCM Client’s Windows local policy (Local Policy –>Computer Configuration –>Administrative Templates –>Network –>Background Intelligent Transfer Service (BITS) –>Limit the maximum network bandwidth for BITS background transfers):

image

As you can see, the SCCM site setting is identical to SCCM client’s local policy. SCCM 2007 Unleashed has explained the client BITS settings. You can read about it on Google Books HERE.

The book did not state and explain the SCCM client actually WRITES the SCCM site’s BITS policy into SCCM client’s Windows local group policy object (GPO). So I did below tests IN ORDER in my home SCCM 2007 R3 AND SCCM 2012 RTM test environments to work out the behaviours of SCCM client and compare SCCM Client’s BITS setting against the above mentioned setting in local policy:

1. SCCM Client BITS setting left as default in SCCM (Not configured).

  • SCCM 2007 Client Computers: BITS policy in local GPO is set to DISABLED!
  • SCCM 2012 Client Computers: Same as SCCM 2007 client computers

2. Enable BITS in SCCM Computer Client Agent setting (In 2007, apply to both clients and BDPs, in 2012, just enable it since there is no BDPs in 2012 anymore.), and define some throttling settings. Then trigger machine policy retrieval on SCCM client computers.

  • SCCM 2007 Client Computers: BITS policy in local GPO is ENABLED in throttling settings are set to as same as SCCM policy.
  • SCCM 2012 Client Computers: Same as SCCM 2007 client computers

3. Change BITS throttling settings in SCCM. Then trigger machine policy retrieval on SCCM client computers

    • SCCM 2007 Client Computers: BITS policy in local GPO updated accordingly.
    • SCCM 2012 Client Computers: Same as SCCM 2007 client computers

4. Change BITS throttling settings in SCCM client’s Windows local policy. Then trigger machine policy retrieval on SCCM client computers.

    • SCCM 2007 Client Computers: local policy remained the same after machine policy retrieval.
    • SCCM 2012 Client Computers: Same as SCCM 2007 client computers

5. Change BITS throttling settings in SCCM again. Then trigger machine policy retrieval on SCCM client computers.

  • SCCM 2007 Client Computers: local policy was updated again according to SCCM client’s BITS policy.
  • SCCM 2012 Client Computers: Same as SCCM 2007 client computers

Conclusions:

Based on the tests I have performed. I have come to below conclusions:

  1. When the SCCM client’s BITS policy is not configured, the  BITS throttling settings OS local policy is set to DISABLED, so effectively no BITS throttling is allowed for ALL the apps that uses BITS on the SCCM client computer. (i.e. in our case, VMM agent)
  2. Upon SCCM policy change, SCCM client changes local policy with updated settings once it has retrieved the updated policy via SCCM client’s machine policy retrieval (by default runs every 60 minutes).
  3. The SCCM client’s BITS settings are NOT enforced in local policy. i.e. when local policy is manually updated to be different than SCCM client’s policy, SCCM client does not enforce and update local policy. SCCM clients ONLY write to local policy when the SCCM BITS policy is CHANGED on the primary site.
  4. SCCM 2007 clients and SCCM 2012 clients exhibit same behaviour.

So, please look out if you have other apps that uses BITS and the bandwidth is throttled. SCCM client would update the local policy without you knowing it.

Alternatively, using a domain GPO to set BITS throttling settings seems like a good idea. By doing so, you can target different SCCM clients more granularly (targeting different OUs, using WMI filters and AD groups to set GPO scopes) whereas in SCCM 2007, this setting is unique across all clients in the primary site. Additionally, domain GPO will override local policy so local policy can be ignored.