Thursday, November 16, 2017

Reset Orphaned AdminSDHolder objects

I recently discovered some domain objects which had once been a member of a protected group.  In case you didn't know, Active Directory users have a flag called "AdminCount" which is set to 1 when the user is added to a protected group.  In Windows 2008 ADDS and up, these groups are:

Account Operators
Administrators
Backup Operators
Domain Admins
Domain Controllers
Enterprise Admins
Print Operators
Read-only Domain Controllers
Replicator
Schema Admins
Server Operators

In addition, there are two accounts which are also protected:

Administrator
Krbtgt

You can read more about adminSDholder on technet, but in summary any object with this flag has its ACL overwritten with a copy from the SDholder object located in the System container.  Inheritance is also blocked on these accounts so that their ACLs do not get overwritten by another method.
And the article took me to a support article detailing behavior you might expect if you have this happening to accounts which once were members of a protected group and a vbscript on how to clean up that.


The real solution is probably to delete these accounts, but that was not feasible in this situation so I set out to recreate that vbscript's functionality in PS.  Using the AD cmdlets, it becomes so much more elegant...



$adminusers = Get-ADuser -LDAPFilter "(&(objectcategory=person)(objectClass=user)(admincount=1))" | where {$_.name -ne "krbtgt" -and $_.name -ne "Administrator"} 

ForEach($user in $adminusers) {

     Set-ADuser -Identity $user -replace @{Admincount = 0}

     $dACL = Get-ACL ($user.DistinguishedName) 

     $dACL.SetAccessRuleProtection($false,$false)

     Set-ACL -Path $user.DistinguishedName -AclObject $dACL

    }

Wow!  That's much better. 



Don't do this in your production environment unless you really know the full consequences. 

Friday, July 28, 2017

Monitor an Exchange Mailbox with Powershell...well, mostly EWS and .NET

Recently, I had a use case to start a workflow based on an email sent to an inbox.  After a brief search I determined that there's not really a way to do this with PowerShell.  Sometimes our reach exceeds PowerShell's grasp and for those times it is fortunate that PowerShell can play so well with .NET.

To accomplish this task, it is necessary to install the Exchange Web Service API.  You can find it here:
Download Microsoft Exchange Web Services Managed API 2.2
You can find the full documentation here:
Start using web services in Exchange

The first step is to download and install the EWS API.  Once that is done, you'll need to load it to gain its powers.


# Load the EWS Managed API

$dllpath = "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"

[void][Reflection.Assembly]::LoadFile($dllpath)

Once you've got it loaded, we can create an object in PowerShell and start manipulating it.  If you are working with .NET objects, then you need to head to the MSDN and do some research on the class you are trying to manipulate.  In this case, we are going to use the Exchange Service class.  We are looking at the ExchangeService constructor and we are going to specify the version of EWS we need when calling the class (2013 in this case).

Reading the documentation on the ExchangeService class, we can see that we have options for authentication.  In my case, I needed to pass credentials through to this, rather than use the credentials of the user running the script.  For example purposes, I'm just going to provide a PSCredential object by calling the Get-Credential cmdlet and inputting credentials with access to the account I'm using:

Note that looking at the documentation for the ExchangeServiceBase.Credentials property we need to convert the PSCredential object to a Network Credential.

Once we've got authentication, we need an endpoint to auth to.  The EWS autodiscover endpoint can help us and there's a handy method on the ExchangeService class that we can use to connect to it with the mailboxname.


$exchangeService  = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)

# Set Credentials in Exchange WebService Object

$ExchangeAdminCreds = Get-Credential

$exchangeService.Credentials = $ExchangeAdminCreds.GetNetworkCredential()

# Autodiscover to determine EWS URL

$mailboxname = "YourMailbox@yourexchange.com"

$exchangeService.AutodiscoverUrl($mailboxName)

For my needs, I wanted to monitor the inbox of this folder.  I discovered that to connect to the inbox, we needed to go up a level to the Microsoft.Exchange.WebServices.Data namespace and find the WellKnownFolderName enumeration, and then use this with the mailbox name to get the ID with Microsoft.Exchange.WebServices.Data.FolderId constructor.  Once you've got that, you can bind to the folder.  Sometimes, you'll need to work backwards to determine how these work together (start at the action you need to perform and read what it requires).


# Load type for WellKnownFolderName

$inboxFolderName = [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox

# Identify FolderID from target mailbox 

$InboxFolderID =  New-Object -TypeName Microsoft.Exchange.WebServices.Data.FolderId -argumentlist $inboxFolderName, $mailboxName

# Bind to the Inbox folder of the target mailbox

$inboxFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($exchangeService,$inboxFolderID)

You're thinking, great - now I've got my binding to the folder and I'm ready to start getting emails.  Nope, we've still got some more work to do.  It might be a good idea, depending on your needs to not get a billion hits.  You can limit this with an itemview.  Additionally, we'll need to set up a searchfilter to find the results we need.  For this example, I need to limit by multiple criteria.  This requires a SearchFilterCollection.  To create that, we look at its constructor and work backwards for what we need (datetimerecieved and from for example).


# Optional: reduce the query overhead by viewing the inbox 10 items at a time

$itemView = New-Object -TypeName Microsoft.Exchange.WebServices.Data.ItemView -ArgumentList 10

# Load Types for creating search filter. 

$dateTimeItem = [Microsoft.Exchange.WebServices.Data.ItemSchema]::DateTimeReceived

$from = [Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::From

$searchbefore = (Get-Date).AddMinutes(-1)

$searchafter = (Get-Date).AddDays(-2)

$emailfrom = "someaddress@somewhere.com"

$searchFilter = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsGreaterThanOrEqualTo -ArgumentList $dateTimeItem,$searchafter

$searchFilter1 = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsLessThanOrEqualTo -ArgumentList $dateTimeItem,$searchbefore

$searchFilter2 = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo -ArgumentList $from,$emailfrom

$SearchFilterCollection = New-Object -TypeName Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection -ArgumentList "And",$searchFilter

$SearchFilterCollection.Add($searchFilter1)

$SearchFilterCollection.Add($searchFilter2)

Now, we can finally put this all together using the FindItems method of the ExchangeService class.  As I mentioned earlier, the way to navigate this is to find what you need to do and work backwards.
The $FoundItems variable contains an array of emails which should match your search criteria.  I'll leave it to you to manipulate those and explore MSDN. 

# Use FindItems method with arguments FolderID,SearchFilter,ItemView

$FoundItems = $exchangeService.FindItems($inboxFolder.Id,$searchFilterCollection,$itemView)


Thursday, December 15, 2016

Backup Unsealed OpsMan Management Packs

The best practice for SCOM when importing a new management pack is to create a new unsealed management pack.  I typically name it similarly with a suffix "_overrides".  I then put any overrides related to the rules or objects in the management pack in the unsealed management pack.  In our environment we have our devices monitored by two OpsMan environments - a test and a production  environment.  We install and tweak management packs in the test environment and when we are satisfied they are not too chatty, both the sealed and unsealed management packs are imported into production. 
I wanted to orchestrate the backup of these management packs so that any modifications would not be lost, so I wrote a script to find any unsealed management packs and export them to a unc path.  Here's the script:


<#
.SYNOPSIS – Backup management packs in a OpsMan 2012 Environment 

.DESCRIPTION – This script is run on a schedule and is used to backup unsealed SCOM Managementpacks.

.PARAMETER OpsManServer - FQDN of Operations Manager Management server

.PARAMETER BackupPath - UNC path to backup management packs

.PARAMETER OpsManCredential - PSCredential object for account with rights on OpsMan Server

#>
Param (

[string]$OpsManServer,

[string]$BackupPath,

[PScredential]$OpsManCredential

)

Try{

# Import OperationsManager module

Import-Module OperationsManager
    
# Open persistent connection to Operations Manager Management Server

New-SCOMManagementGroupConnection -ComputerName $OpsManServer -Credential $OpsManCredential | Set-SCOMManagementGroupConnection
    
# Get All unsealed MananagementPacks (assume these are custom)

$CustomMps = Get-SCOMManagementPack -ComputerName $OpsManServer -Credential $OpsManCredential | where {$_.sealed -eq $false} 

# Export each MP to the specified path 

ForEach ($CustomMP in $CustomMPs){
    
    Export-SCOMManagementPack -ManagementPack $CustomMP -path $BackupPath
    
    }
    
# Close Connection to OpsMan Server

Get-SCOMManagementGroupConnection | Remove-SCOMManagementGroupConnection 

}
Catch{
    
        Write-Error -Message "Failed to backup managment packs on $OpsManServer"

        Write-Error -Message $error
       
        $Result = "Failed to remove a SCOM Agent for $SCOMClientName from Managment Server $SCOMManagementServer"
}

$Result

Tuesday, October 20, 2015

Invoke SCCM Client Tasks

I recently had need to invoke the location services task on an SCCM client, and wanted to explore how WMI might be used to call this task.
I knew the client used the root\ccm namespace and after some digging, I found that the SMS_Client Class contained the TriggerSchedule method.  I used wbemtest.exe (available on any machine with WMI) and good old fashioned googling to poke around.  The SCCM 2012 r2 Toolkit contains a file called  SendScheduleMethods.xml, which has a full list of methods you can call (I've included them at the end).

Here's a quick and dirty oneliner I used to invoke my Location Services task:
  1. Invoke-CimMethod -ClassName SMS_Client -MethodName TriggerSchedule -Arguments @{sScheduleID='{00000000-0000-0000-0000-000000000025}'}  -Namespace root/ccm​
I checked the ClientLocation.log in C:\Windows\CCM\Logs and it was indeed up and running.  Fun times were had.  ​

{00000000-0000-0000-0000-000000000001}
HARDWARE_INV_ACTION_ID
Hardware Inventory
{00000000-0000-0000-0000-000000000002}
SOFTWARE_INV_ACTION_ID
Software Inventory
{00000000-0000-0000-0000-000000000003}
DISCOVERY_INV_ACTION_ID
Discovery Inventory
{00000000-0000-0000-0000-000000000010}
FILE_COLLECTION_ACTION_ID
File Collection
{00000000-0000-0000-0000-000000000011}
IDMIF_COLLECTION_ACTION_ID
IDMIF Collection
{00000000-0000-0000-0000-000000000012}
CLIENT_MACHINE_AUTH_ACTION_ID
Client Machine Authentication
{00000000-0000-0000-0000-000000000021}
POLICYAGENT_REQUEST_MACHINE_ASSIGNMENTS_ID
Request Machine Assignments
{00000000-0000-0000-0000-000000000022}
POLICYAGENT_EVALUATE_MACHINE_POLICIES_ID
Evaluate Machine Policies
{00000000-0000-0000-0000-000000000023}
LS_SCHEDULEDCLEANUP_REFRESH_DEFAULT_MP_TASK_ID
Refresh Default MP Task
{00000000-0000-0000-0000-000000000024}
LS_SCHEDULEDCLEANUP_REFRESH_LOCATIONS_TASK_ID
LS (Location Service) Refresh Locations Task
{00000000-0000-0000-0000-000000000025}
LS_SCHEDULEDCLEANUP_TIMEOUT_REFRESH_TASK_ID
LS (Location Service) Timeout Refresh Task
{00000000-0000-0000-0000-000000000026}
POLICYAGENT_REQUEST_USER_ASSIGNMENTS_ID
Policy Agent Request Assignment (User)
{00000000-0000-0000-0000-000000000027}
POLICYAGENT_EVALUATE_USER_POLICIES_ID
Policy Agent Evaluate Assignment (User)
{00000000-0000-0000-0000-000000000031}
SWMTR_USER_REPORT_GENERATION_ID
Software Metering Generating Usage Report
{00000000-0000-0000-0000-000000000032}
SOURCE_UPDATE_MESSAGE_ID
Source Update Message
{00000000-0000-0000-0000-000000000037}
Schedule for clearing proxy settings cache
Clearing proxy settings cache
{00000000-0000-0000-0000-000000000040}
[Machine Policy schedules] PolicyAgent_Cleanup
Machine Policy Agent Cleanup
{00000000-0000-0000-0000-000000000041}
[User Policy schedules] PolicyAgent_Cleanup
User Policy Agent Cleanup
{00000000-0000-0000-0000-000000000042}
[Machine Policy schedules] PolicyAgent_RequestAssignments
Policy Agent Validate Machine Policy / Assignment
{00000000-0000-0000-0000-000000000043}
[User Policy schedules] PolicyAgent_RequestAssignments
Policy Agent Validate User Policy / Assignment
{00000000-0000-0000-0000-000000000051}
Schedule for retrying/refreshing certificates in AD on MP
Retrying/Refreshing certificates in AD on MP
{00000000-0000-0000-0000-000000000061}
PDP_STATUS_REPORTING_SCHEDULE_ID
Peer DP Status reporting
{00000000-0000-0000-0000-000000000062}
PDP_PENDING_PACKAGE_CHECK_SCHEDULE_ID
Peer DP Pending package check schedule
{00000000-0000-0000-0000-000000000063}
SUM_UPDATES_INSTALL_SCHEDULE_ID
SUM Updates install schedule
{00000000-0000-0000-0000-000000000071}
NAP_ACTION_ID
NAP action
{00000000-0000-0000-0000-000000000101}
HARDWARE_INV_POLICY_ACTION_ID
Hardware Inventory Collection Cycle
{00000000-0000-0000-0000-000000000102}
SOFTWARE_INV_POLICY_ACTION_ID
Software Inventory Collection Cycle
{00000000-0000-0000-0000-000000000103}
DISCOVERY_INV_POLICY_ACTION_ID
Discovery Data Collection Cycle
{00000000-0000-0000-0000-000000000104}
FILE_COLLECTION_POLICY_ACTION_ID
File Collection Cycle
{00000000-0000-0000-0000-000000000105}
IDMIF_COLLECTION_POLICY_ACTION_ID
IDMIF Collection Cycle
{00000000-0000-0000-0000-000000000106}
METRING_POLICY_ACTION_ID
Software Metering Usage Report Cycle
{00000000-0000-0000-0000-000000000107}
SOURCE_UPDATE_POLICY_ACTION_ID
Windows Installer Source List Update Cycle
{00000000-0000-0000-0000-000000000108}
SOFTWARE_UPDATES_POLICY_ACTION_ID
Software Updates Assignments Evaluation Cycle
{00000000-0000-0000-0000-000000000109}
PDP_MAINTENANCE_POLICY_ACTION_ID
Branch Distribution Point Maintenance Task
{00000000-0000-0000-0000-000000000110}
DCM_POLICY_ACTION_ID
DCM policy
{00000000-0000-0000-0000-000000000111}
STATE_SYSTEM_POLICY_BULKSEND_ACTION_ID
Send Unsent State Message
{00000000-0000-0000-0000-000000000112}
STATE_SYSTEM_POLICY_CACHECLEANOUT_ACTION_ID
State System policy cache cleanout
{00000000-0000-0000-0000-000000000113}
UPDATE_SOURCE_POLICY_ACTION_ID
Scan by Update Source
{00000000-0000-0000-0000-000000000114}
UPDATE_STORE_POLICY_ACTION_ID
Update Store Policy
{00000000-0000-0000-0000-000000000115}
STATE_SYSTEM_POLICY_BULKSEND_HIGH_ACTION_ID
State system policy bulk send high
{00000000-0000-0000-0000-000000000116}
STATE_SYSTEM_POLICY_BULKSEND_LOW_ACTION_ID
State system policy bulk send low
{00000000-0000-0000-0000-000000000120}
AMT_STATUS_CHECK_POLICY_ACTION_ID
AMT Status Check Policy
{00000000-0000-0000-0000-000000000121}
APPMAN_POLICY_ACTION_ID
Application manager policy action
{00000000-0000-0000-0000-000000000122}
APPMAN_USER_POLICY_ACTION_ID
Application manager user policy action
{00000000-0000-0000-0000-000000000123}
APPMAN_GLOBAL_EVALUATION_ACTION_ID
Application manager global evaluation action
{00000000-0000-0000-0000-000000000131}
PWRMGMT_START_SUMMARIZER_ID
Power management start summarizer
{00000000-0000-0000-0000-000000000221}
EP_DEPLOYMENT_REEVALUATE_ID
Endpoint deployment reevaluate
{00000000-0000-0000-0000-000000000222}
EP_AMPOLICY_REEVALUATE_ID
Endpoint AM policy reevaluate
{00000000-0000-0000-0000-000000000223}
EXTERNAL_EVENT_DETECTION_ID
External event detection

Sunday, August 3, 2014

Change Client Cache Size using DCM in SCCM 2012R2

Recently, I needed to set the cache size on our clients to push out some large applications.  There are many ways to accomplish this, but I thought it would be a good way to test out the Desired Configuration Management feature in SCCM 2012.  Configuration Management approaches computer management with a similar approach as the Application model in SCCM in that we have a method to ensure compliance.

Group Policy allows to push out a setting to your organization, but does not offer reporting about whether the change actually took place nor does it necessarily prevent someone on the workstation from changing that setting.  DCM allows you to define the setting and will check to ensure that it is correctly set using a remediation method you define on a schedule that you can define.  Along with Powershell Desired State Configuration, this is a paradigm shift in systems administration designed to prevent configuration "drift" and ensure standardization across your organization.

So, DCM is cool and probably something to look into.  I can't imagine it will take the place of group policy, but I think it could be an important tool to have knowledge of and access to.  Here's how I used it to ensure that our clients had a standardized cache size.

Start by navigating to the Assets and Compliance Node, expanding the "Compliance Settings" folder and selecting "Create Configuration Item".


On the next tab, give your configuration item a Name and a Description if you so choose  Select Windows as they type for this configuration item.  On the next tab, you can select which operating systems this applies to, but for our purposes, leave them all checked.
For this post, I'm leveraging the ccm namespace in WMI and retrieving the client cache from that via a Powershell script.  We'll also use Powershell to remediate non-compliant machines.

Create a new Setting, give it a name, and select "Script" as the Setting type and String as the Data type.
Click "Add Script..." under the Discovery Script section and paste the following code:

This will return the size of the client's cache.

Click "Add Script..." under the Remediation Script section and paste the following code:


This will run on non-compliant clients and set their cache size to whatever you need.  This is case, I changed it to 10GB by setting $cachesize = "10240".
Now, switch to the Compliance Rules tab and create a new compliance rule.
This will check for the value returned by our Discovery Script and consider all values other than 10240 as non-compliant and then subsequently run the Remediation Script to change the value to 10240.

We cannot deploy Configuration Items directly to a collection, they must be first assigned to a Configuration Baseline, so navigate to the Assets and Compliance node, expand the Compliance Settings folder and Configuration Baseline and select "Create Configuration Baseline", give it a name and then click "Add", select "Configuration Item" and select what we just created.
Click on your newly created Configuration Baseline and select Deploy.
Make sure that you check "Remediate noncompliant rules when supported" to ensure that the compliance settings will be enforced.  You can set the schedule on which compliance will be set and choose to generate alerts for non-compliance.  Set it to your test collection and we are done.

Friday, July 25, 2014

Failing Registry Detection Method in SCCM 2012 R2

When creating a detection method in SCCM that leverages a Registry entry, SCCM 2012 confusingly offers the option to "Use (Default) registry key value for detection."
Seemingly, this would allow you to target the presence of a Registry Key, by specifying the (Default) value. However, this does not actually work.  You'll need to leave the box unchecked to target a key.  

Even though the registry GUI shows that a (default) value exists for every key, that's not actually the case. 

 In PowerShell, we can see that HKLM\Software\Microsoft does not have any values, while HKLM\Software\7-zip does.  The (Default) key regedit shows is not an actual entry.

I am not sure the reasoning behind adding the Use (Default) registry key value for detection check box, but leaving it unchecked performed the behavior I originally expected.  



Monday, July 14, 2014

Set Local Account Password with PowerShell


As some of you may be aware, the ability to set a password in Group Policy was recently disabled by a security update (http://support.microsoft.com/kb/2962486/en-us).  My organization has relied on this functionality for creating/administering local accounts on domain machines and I thought others on campus might be in the same boat.  I’ve created a PowerShell script to set a password in our environment and thought the rest of you might be able to use it/adapt it for your needs. 

It can set the local password of any account you specify and can be set to look for all machines in a domain, to use an LDAP filter to identify machines with a certain name (or any LDAP query) and set against a SearchBase in which to perform the query, or can be run against a csv of computer names.

It spits out a report which identifies which machines did not respond to a ping request/were offline and if it succeeded or failed on a machine (it records the error if one is produced). 

It does have limitations
·         Can only be run against machines with PowerShell 2.0 and up (everyone has Windows 7 right?)
·         PSremoting must be enabled and requisite ports opened (can be done from Group Policy)
·         If you would like to use the LDAP search feature (which it does by default) you’ll need to run it from a machine with the ActiveDirectory PowerShell module available.  
·         Also be sure to check that the account you run it from has admin privileges on the target machines, and check your PowerShell Execution policy. 

More specific instructions on its functionality are available in comments of the script itself.