Archive for the 'Examples' Category

Find users in too many groups

Large Kerberos tokens (caused by too many groups listed in them) can be an issue in some environments (I’ve just had a similar trouble myself in an ADFS deployment). Luckily PowerShell is here to help. This quick script will list all users who are members of more than 75 groups:

$limit = 75
Get-QADUser -SizeLimit 0 -DontUseDefaultIncludedProperties |
  ForEach-Object {
    $groups = Get-QADGroup -ContainsIndirectMember $_.DN -SizeLimit $limit `
      -DontUseDefaultIncludedProperties -WarningAction SilentlyContinue
    if ($groups.Count -ge $limit) { $_ }
  }

Here’s a quick overview of what the script is doing:

  1. I assign the limit (75) to a variable. This is just for my convenience of reuse. E.g. I could turn this line into param($limit = 75) – and save this as a parameterized script or turn it into a function.
  2. I user Get-QADUser to retrieve all (-SizeLimit 0) user accounts from my current domain and I make sure to not retrieve any attributes along – so I save memory and improve performance (-DontUseDefaultIncludedProperties)
  3. For each user in my domain, I retrieve the first 75 (-SizeLimit $limit) groups to which the user belongs directly or through nesting (-ContainsIndirectMember $_.DN). There’s obviously no need to retrieve all groups – we just need to know if the user reached the limit. Again, we do not need any attributes (-DontUseDefaultIncludedProperties). I also tell PowerShell to not warn me if there are more groups than the size limit I specified (-WarningAction SilentlyContinue).
  4. Finally, if indeed we reached the limit, I output that user object.

You can obviously then just see the list on the screen or output it to CSV or HTML report.

Tags: , , , , , , ,

Add filepath to ConvertTo-HTML

Converting PowerShell data into an HTML report and save it to disk with no need for extra pipeline has long been my dream. Unfortunately, there’s no native Export-HTML cmdlet (unlike, say, Export-CSV), and ConvertTo-HTML does not have -Path parameter and only displays the html code on the screen (very useful ;) ) unless you pipe it to Out-File.

So being inspired by Kirk adding parameters to Import-CSV and using PowerShell 2.0 code-snippets, I created my Export-HTML function, which behaves exactly like ConvertTo-HTML but adds optional -Path parameter to specify the output file.

Download it, copy/paste the function into PowerShell (or dot-source it, or include it in your PowerShell profile) and you will be able to do something like:

Get-Process | Export-Html -Path C:\pr.htm

or

Get-Process |
    Export-Html C:\pr.htm -Title My Processes

or

Get-Process |
    Export-Html C:\pr.htm -Property Name, Handles -Title My Processes

You can download the code here, or copy/paste it from the text below.

You may also consider renaming the function name from Export-HTML to ConvertTo-HTML (or use set-alias to make them the same thing), because the -Path parameter is optional, and old behavior of outputting HTML code to the console/pipeline is supported as well as all native parameters.

Here’s how I created this proxy function:

  1. Downloaded and installed PowerShell 2.0 code snippets.
  2. Used the function (proxy) snippet to generate the proxy for ConvertTo-
    HTML
    .
  3. Added Path to the parameters section:
  4.     [Parameter(Position=0)]
        [Alias('PSPath', 'FilePath')]
        [ValidateNotNullOrEmpty()]
        [System.String]
        ${Path},
    
  5. Added a variable to store modified PowerShell code to be executed:
  6. $scriptCmdPipeline = ''
  7. Added parameter handling in which I (if Path is present) append the Out-File code to the pipeline:
  8.         if ($Path) {
                $PSBoundParameters.Remove('Path') | Out-Null
                $scriptCmdPipeline += " | Out-File -FilePath $Path"
            }
    
  9. Got the original command-line for ConvertTo-HTML
  10.         $scriptCmd = {& $wrappedCmd @PSBoundParameters}
    
  11. And added this new pipeline to it:
  12.         $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
                    [string]$scriptCmd + $scriptCmdPipeline
                )
    

The rest was handled by the code snippet.

Here’s the resultant code:

#Requires -Version 2.0

<#
    Export-Html behaves exactly like native ConvertTo-HTML
    However it has one optional parameter -Path
    Which lets you specify the output file: e.g.
    Get-Process | Export-Html C:\temp\processes.html
#>

function Export-Html {
[CmdletBinding(DefaultParameterSetName='Page')]
param(
    [Parameter(ValueFromPipeline=$true)]
    [System.Management.Automation.PSObject]
    ${InputObject},

# Adding Path parameter 
# (made it Position 0, and incremented Position for others)
    [Parameter(Position=0)]
    [Alias('PSPath', 'FilePath')]
    [ValidateNotNullOrEmpty()]
    [System.String]
    ${Path},

    [Parameter(Position=1)]
    [ValidateNotNullOrEmpty()]
    [System.Object[]]
    ${Property},

    [Parameter(ParameterSetName='Page', Position=4)]
    [ValidateNotNullOrEmpty()]
    [System.String[]]
    ${Body},

    [Parameter(ParameterSetName='Page', Position=2)]
    [ValidateNotNullOrEmpty()]
    [System.String[]]
    ${Head},

    [Parameter(ParameterSetName='Page', Position=3)]
    [ValidateNotNullOrEmpty()]
    [System.String]
    ${Title},

    [ValidateSet('Table','List')]
    [ValidateNotNullOrEmpty()]
    [System.String]
    ${As},

    [Parameter(ParameterSetName='Page')]
    [Alias('cu','uri')]
    [ValidateNotNullOrEmpty()]
    [System.Uri]
    ${CssUri},

    [Parameter(ParameterSetName='Fragment')]
    [ValidateNotNullOrEmpty()]
    [Switch]
    ${Fragment},

    [ValidateNotNullOrEmpty()]
    [System.String[]]
    ${PostContent},

    [ValidateNotNullOrEmpty()]
    [System.String[]]
    ${PreContent})

begin
{
    try {
        $outBuffer = $null
        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
        {
            $PSBoundParameters['OutBuffer'] = 1
        }
        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('ConvertTo-Html',
            [System.Management.Automation.CommandTypes]::Cmdlet)

        # define string variable to become the target command line
        #region Initialize helper variable to create command
        $scriptCmdPipeline = ''
        #endregion

        # add new parameter handling
        #region Process and remove the Path parameter if it is present
        if ($Path) {
            $PSBoundParameters.Remove('Path') | Out-Null
            $scriptCmdPipeline += " | Out-File -FilePath $Path"
        }
        #endregion

        $scriptCmd = {& $wrappedCmd @PSBoundParameters}

        # redefine command invocation
        #region Append our pipeline command to the wrapped command script block
        $scriptCmd = $ExecutionContext.InvokeCommand.NewScriptBlock(
                [string]$scriptCmd + $scriptCmdPipeline
            )
        #endregion

        $steppablePipeline =
          $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
        $steppablePipeline.Begin($PSCmdlet)
    } catch {
        throw
    }
}

process
{
    try {
        $steppablePipeline.Process($_)
    } catch {
        throw
    }
}

end
{
    try {
        $steppablePipeline.End()
    } catch {
        throw
    }
}
<#

.ForwardHelpTargetName ConvertTo-Html
.ForwardHelpCategory Cmdlet

#>}

Hope you find this useful and it gets you the feature you wanted without waiting for PowerShell v3. ;)

Tags: , , , , , , ,

Get a list of users’ email addresses

Here’s a one-liner to turn members of a group into a list of email addresses, separated by semicolon. I am using it every now and then when someone from our partners (which obviously do not have access to our address book) ask me for a list of folks to include in some discussions, or grant access to some resources, and so on.

Here’s the oneliner (for PowerShell v2):

(Get-QADGroupMember MyGroupName -Type user -Indirect |
    Select -expand Email) -join ';'

PowerShell v1 version has a slightly different syntax for join:

[string]::join(';',
  (Get-QADGroupMember MyGroupName -Type user -Indirect |
    Select -expand Email))

And here’s a quick explanation of what it does:

  • I use Get-QADGroupMember to retrieve all members of the group. Note that -Indirect parameter gives me all members of nested groups, and -Type user makes sure that nested groups themselves get excluded.
  • Then I am taking the collection of user objects and turn that into a collection of just one property of the objects (Email) using  Select -expand.
  • Finally I am using join to turn that collection into a string and using semicolon as separator.

Hope this is useful.

List all empty OUs

Here’s a one-liner you can use to quickly find empty organizational units in your Active Directory:

Get-QADObject -Type organizationalUnit -DontUseDefaultIncludedProperties |
  where {
    -not ( Get-QADObject -SearchRoot $_.DN -DontUseDefaultIncludedProperties `
    -SearchScope OneLevel -SizeLimit 1 -WarningAction SilentlyContinue )
  }

A quick explanation of what I am doing here:

  1. I am retrieving all organizationalUnit objects from my domain (and use the -DontUseDefaultIncludedProperties switch to save a few milliseconds ;) )
  2. Then for each of the OUs I am retrieving all AD objects that are in that OU by doing a Get-QADObject and limiting the search scope to the DN of the current OU.
  3. Note that (like we did when looking for large groups) I am using the -SizeLimit parameter to see if I can get 1 item in the call (all I need is to learn whether there is anything in the OU – I don’t need the whole list) – which obviously makes the whole script magnitudes of time faster. I use -SearchScope
  4. Based on Kirk’s recommendation I am using -SearchScope OneLevel to exclude the OU itself.
  5. I am using -not operator so I get only the OUs for which this Get-QADObject evaluates to $null (nothing found) and thus -not $null evaluates to $true.

P.S. This is the code I was using initially, which I then corrected based on Kirk’s comments below:

$emptyOUs = Get-QADObject -Type organizationalUnit -DontUseDefaultIncludedProperties | where {(Get-QADObject -SearchRoot $_.DN -SizeLimit 2 -DontUseDefaultIncludedProperties).Count -lt 2}

Tags: , , , , , ,

Fastest way to retrieve AD objects

DontUseDefaultIncludedProperties is the AD cmdlets parameter you need when you want to get AD objects fast without extra properties you do not need. For example, I have just used it in my script to locate the largest groups in our Active Directory. Let’s talk about how effective it really is and how it works under the covers.

First, of all, let me prove that it is indeed very efficient. Here’s the same Get-QADUser run 100 times with and without the parameter:

[PS] C:\>Measure-Command { for ($i=0;$i -lt 100;$i++) { $a = Get-QADUser -SamAccountName dsotnikov -DontUseDefaultIncludedProperties } }

...
Seconds : 3
Milliseconds : 951
...


[PS] C:\>Measure-Command { for ($i=0;$i -lt 100;$i++) { $a = Get-QADUser -SamAccountName dsotnikov } }

...
Seconds : 7
Milliseconds : 526
...

That’s twice as fast with the parameter than it is without it!

Why? Because DontUseDefaultIncludedProperties makes the cmdlet only retrieve 2 attributes: distinguishedName и objectClass, whereas the cmdlet without it will go get quite a few other properties.

You can easily see which attributes got retrieved by running:
$a = Get-QADUser -SamAccountName dsotnikov -DontUseDefaultIncludedProperties
$a.Cache.AttributesInCache

The interesting thing is that the cmdlet is even smarter with subsequent use of the object. For most properties (to be specific, for all regular .NET properties of the object but not PowerShell dynamic NoteProperties), we will go and retrieve the property once you request it later on. E.g. this will actually work and give you the account description:

$a = Get-QADUser -SamAccountName dsotnikov -DontUseDefaultIncludedProperties
$a.Description

And this will retrieve a whole bunch of attributes:
Get-QADUser -SamAccountName dsotnikov -DontUseDefaultIncludedProperties | Format-List *

You can obviously keep using $a.Cache.AttributesInCache to check which ones we retrieve.

Pretty cool, isn’t it?

The only other thing I would note is the difference between:

Get-QADUser -SamAccountName dsotnikov -DontUseDefaultIncludedProperties

and

Get-QADUser dsotnikov -DontUseDefaultIncludedProperties

The former is way more efficient than the latter because the -SamAccountName parameter (or any other parameter besides the generic implied -Identity) lets us optimize the query specifically to search by that attribute rather than do the Ambiguous Name Resolution which we use otherwise. Be specific in your parameters and we will give you the fastest results!

Tags: , , , , , ,

Find large AD groups

Here’s a quick script which I wrote yesterday for one of our professional services engineers. Basically they wanted to give customer a report for large groups, i.e. groups with more than certain number of user accounts including the ones in nested groups.

This turned out to be very straight-forward with QAD cmdlets.

The script is below:

function Get-LargeADGroup {
param($limit = 75)

 Get-QADGroup | Foreach-Object {
   $members = $_ |
           Get-QADGroupMember -Indirect -Type 'user' `
              -DontUseDefaultIncludedProperties `
              -SizeLimit ($limit+1) -WarningAction SilentlyContinue
   if ( ($members -ne $null) -and
        ($members.gettype().Name -eq 'Object[]') -and
        ($members.Count -ge $limit)) {
     $_
   }
 }
}

#Usage
Get-LargeADGroup -limit 75 | Select Name, DN | Export-Csv c:\largegroups.csv

One trick worth pointing out is the use of -SizeLimit to not retrieve all members of the group – after all I just need to know if there is more than certain number of them.

Hope this helps! :)

What was that group again?

A simple one-liner can help. Just earlier today I could not remember the name of a particular distribution list but knew a couple of its members. The quick one-liner to help me out was:

Get-QADGroup -ContainsMember ('User A', 'User B')

And while we are on it, here’s an even better one-liner if you want to find a DL which has all direct reports of, say, your company’s VP:

Get-QADGroup -ContainsMember (Get-QADUser -Manager 'User C')

And obviously if such a DL does not exist, you should probably just go ahead and create it ;)

New-QADGroup -Name 'Top Managers' -Member (Get-QADUser -Manager 'User C')

I love PowerShell. :)

Tags: , , , , ,

Detect AD schema version from PowerShell

Just wanted to share a quick one-liner to check Active Directory domain and forest functional level with AD cmdlets:

[PS] C:\ >Get-QADRootDSE | Format-List *

DomainControllerFunctionality : Windows2003
DomainFunctionality : Windows2003
ForestFunctionality : Windows2003
IsGlobalCatalogReady : True
... and so on ...

Quick, easy and comes quite handy. Besides the AD schema info there’s other useful information – so definitely worth adding to your arsenal.

[UPDATE] Shay and MoW both contacted me saying that I did not include the one-liner which would give you the exact version number. Here you go:

(Get-QADObject (Get-QADRootDSE).SchemaNamingContext -IncludedProperties objectVersion).objectVersion

Two-Factor Authentication with PowerShell

Another Quest product turned out to be PowerShell-enabled – Quest Defender. Defender is basically a two-factor authentication solution for network, web and application access supporting various kinds of tokens.

Anyways, the cool thing is that the team did not have to do anything to get PowerShell interface for their product. Because they store all configuration data in AD, QAD cmdlets are more or less all they need.

For example, this simple script lists all tokens and users to which they are assigned:

foreach ($token in
    (Get-QADObject -IncludeAllProperties -Type "defender-tokenClass"))
{ Write-Output $token.Name $token."defender-tokenUsersDNs" }

Dmitry Kagansky has posted this and a few other sample scripts here.

This is a very cool eco-system/re-use example. If your product stores all configuration in Active Directory, you can just re-use AD cmdlets and you are all set.

Well, obviously Dmitry could make things even easier by wrapping his (albeit simple) scripts into PowerShell v2 advanced functions with whatif/confirm support, help, tab-completion (in case of PowerGUI editor, intellisense ;) ) and so on.

Tags: , , ,

Restore Exchange data from PowerShell

Working with mail backups can be a pain. Especially when you need to do some kind of bulk operations like scan backups for particular mailboxes and find all emails with certain keywords and then export then to a PST or add them to a public folder.

Quest has recently PowerShell-enabled its Exchange/Notes/PST backup/recovery product – and we got a lot of great functionality available for your scripts/command line.

Here are a few examples:

  • Here’s how you can attach a standard Exchange backup and retrieve its messages as .msg files:

Attach-RMEExchangeDB -EdbPath "C:\Data\Edb\EdbSearchTest\EdbSearchTest.edb" | Get-RMEMessage | Export-RMEMessage -ExportPath "C:\Data\Exported\Msg" -Type Msg

  • Or look for particular keywords in Lotus Notes data:

Attach-RMELotusDB "C:\Data\Nsf" | Get-RMEMessage -SearchIn Subject, Body -Text "test" | Export-RMEMessage -ExportPath "C:\Data\Exported\Eml" -Type Eml

  • Or restore a folder to PST:

Attach-RMEExchangeDB "C:\Data\Edb\EdbSearchTest\EdbSearchTest.edb" | Get-RMEFolder "John Doe\Inbox" | Restore-RMEFolder -TargetPath "C:\Data\Restored\Pst"
Obviously you can restore to live mailboxes and public folders, work with attachments, and so on, and so forth.

Here’s full list of the cmdlets – each of the pages has examples and details on parameters, etc.:

Note that unlike AD cmdlets these are actually a part of commercial product so there is cost involved. You can get a trial license from the product page. If you are a Microsoft MVP you can also get a free NFR license by applying here.

Next Page »


View Dmitry Sotnikov's profile on LinkedIn

Follow Dmitry Sotnikov at Twitter

My Recent Tweets

Archives

See you at:

Legal

The posts on this blog are provided “as is” with no warranties and confer no rights. The opinions expressed on this site are mine and mine alone, and do not necessarily represent those of my employer Quest Software or anyone else for that matter. All trademarks acknowledged.

© 2007 Dmitry Sotnikov

Pages

 

November 2009
M T W T F S S
« Oct    
 1
2345678
9101112131415
16171819202122
23242526272829
30