SQLServerCentral Article

PowerShell To Get Active Directory Users And Groups into SQL

,

Where I work, we have a number of applications that use Active Directory and a Single Sign On methodology. Each application has a table with the user's login and mappings to various internal roles, used determine what parts of the application the user can access. Also, except for a handful of applications that need SQL logins, SQL Server access is granted via AD Groups or AD Logins.

As a result, when a user reports an issue, we have to start in Active Directory to determine if the user is locked out, disabled, or password expired, before we can even start looking to see if the problem is in an application.

Having a list of Active Directory users in a SQL table can really open up your ability to analyze your systems, and produce smart relevant reports about your user base. In my case, I’m using PowerShell to query Active Directory for several different items, and insert the results into a suite of tables. I’m not going to go deep into PowerShell ins and outs, but hope to give you an incredible tool to put in your toolbox.

The Rabbit Hole

When you put your SQL Hat on, since you grant access to both groups and users, you realize you need tables filled with AD Users, AD groups and a AD Group Members table featuring who belongs in those groups. As you start pulling the AD Groups, you end up getting groups that contain other groups, objects like servers and computers and so much more. Add in multiple domains or forests, and it starts getting scary! The whole process goes pretty deep down that rabbit hole, so you get to benefit from the investigation and solution I’ve built so far.

So for my project, I’m gathering quite a bit of information; I’m getting a list of all Active Directory Users, all the groups, and all the group members. Groups can contain groups, so you might need to create a recursive CTE to enumerate just which GroupMembers is in a given group.

The Core Commands

Here’s a script example that is getting the Top 10 of users and Top 100 groups as an example. The last is enumerating the members in the “users” group, which is often another group name.

Each example is using the  Out-GridView command to throw the data into a grid, which keeps us SQL nerds in our comfort zone.

Import-Module ActiveDirectory
Get-ADUser        -Filter * -ResultSetSize 100  -Properties * | Out-GridView
Get-ADGroup       -Filter * -ResultSetSize 100  -Properties * | Out-GridView
Get-ADGroupMember -Identity "Users"  | Out-GridView

Review the Results

Let’s look at the results for Get-ADUser, since that’s the most important item. Everything else is just more information about our core investigation: the Users in AD.

A side note that is probably relevant: if you ask for specific properties, and you misspell it or it does not exist, you get a blank answer, which can be confusing. In my example below, I included a column “Email”, but AD does not have a property for it; it does have EmailAddress though. In my world, we end up populating that address outside of this process for other reasons.

Get-ADUser -Filter {samaccountname -eq " Leonard_Hofstadter"} -ResultSetSize 10  -Properties * | select -property sAMAccountName,ou,GivenName,SurName,DisplayName,email,emailaddress | Out-GridView

For the most part, what I’m grabbing is very straightforward, since the values are reasonably self describing; GivenName and Surname(FirstName/LastName) are a bit of unusual vernacular, but all other values are pretty understandable.

I’ve added a pair of inline calculations to calculate when an account or password expires, which can be helpful. The values stored in AD are not a datetime value, but number of ticks, which is something that is not easily consumable, so I’m converting them up front.

The final command I actually end up looks like this for reference, and has the properties you see here:

Get-ADUser -Filter *  -Properties * | select -property
  CanonicalName,
  sAMAccountName,
  ou,
  GivenName,
  SurName,
  DisplayName,
  email,
  emailaddress,
  StreetAddress,
  City,
  State,
  PostalCode,
  HomePhone,
  MobilePhone,
  OfficePhone,
  Fax,
  Company,
  Organization,
  Department,
  Title,
  Description,
  Office,
  extensionAttribute1,
  extensionAttribute2,
  extensionAttribute3,
  extensionAttribute4,
  extensionAttribute5,
  @{Name='AccountExpires';Expression={[DATETIME]::fromFileTime($_.accountExpires)}},
  Enabled,
  PasswordLastSet,
  @{n="PasswordExpirationDate";e={$_.PasswordLastSet.AddDays($maxPasswordAge)}},
  PasswordNeverExpires,
  PasswordExpired,
  LastLogonDate,
  whenCreated

Caveats and Gotchas for Folks New to PowerShell

An interesting difference between T-SQL and PowerShell: asterisk (*) does not mean “all” in PowerShell!  As a SQL developer, you expect asterisk (*) to mean everything, but instead, there is a default set of Properties that is returned as part of the asterisk results. You’ll see why in a minute.

The command below returns 30 columns in our grid; that might be an artifact of the Out-Grid command, because you don’t get all values in the grid!

Get-ADUser        -Filter * -ResultSetSize 10  -Properties * | Out-GridView

The same command sent to the text viewer, in my environment, returns 142 values!

Get-ADUser        -Filter * -ResultSetSize 10  -Properties *

But wait, there’s more! In my shop, I know we use some of the twelve optional properties related to the ADUser to store information; extensionAttribute1 through extensionAttribute12 exist, but are empty unless you populate them. I know they exist, but they are not in that 142 items I just grabbed. So instinctively, I already know there are more properties that I’m currently seeing. It depends on your AD environment, but there are actually more than 700 properties related to an ADUser.

If you need to go down that tangent, I’ve added a text file (Get-ADUser_All_Properties.txt) that enumerates all the Properties I saw in my domain. Also, two resources for you to make it easy:

Download Oneidentity (Formerly Quest, formerly Dell) ActiveRoles Management Shell for Active Directory 1.7: https://www.oneidentity.com/products/active-roles/

Dmitry Sotniko’s blog has an example using the above script to get a list of ALL user properties: https://dmitrysotnikov.wordpress.com/2007/06/28/get-a-list-of-all-user-properties/

My own script adaptation of those two resources is below:

###################################################################################################
#load snapin if not already loaded
# https://support.software.dell.com/download-install-detail/5024645
###################################################################################################
if ( (Get-PSSnapin -Name Quest.ActiveRoles.ADManagement -ErrorAction SilentlyContinue) -eq $null )
{
    Add-PsSnapin Quest.ActiveRoles.ADManagement
}
Get-QADUser -ReturnPropertyNamesOnly -IncludeAllProperties

What if we have multiple forests or child domains?

My company was recently absorbed by another company. Get-ADUser has an optional –Server parameter that you can use to identify the other domains. In my example, My Home domain is the BigBang domain was absorbed by the Disney domain.

I know that fully qualified SamAccountName are actually something like BigBang.com\Leonard_Hofsteader, but the other domain is actually users like Disney.local\Aladdin_StreetRat. So, I could add the parameter to the GetADUser call to pull the other domain:

Get-ADUser        -Filter * -ResultSetSize 10  -Server bigbang.com -Properties * | Out-GridView
Get-ADUser        -Filter * -ResultSetSize 10  -Server disney.local -Properties * | Out-GridView

I don’t have child domains set up, but it would be the same logic; identify the child domain, and pass that to the server;

Get-ADUser        -Filter * -ResultSetSize 10  -Server Avengers.MarvelUniverse.com -Properties * | Out-GridView
Get-ADUser        -Filter * -ResultSetSize 10  -Server XMen.MarvelUniverse.com -Properties * | Out-GridView

PowerShell to SQL Time!

OK, so I rambled on about how there is a LOT of info out there, but what is relevant? I recommend you start with what I found relevant to me, and then add or subtract other properties that might be useful.  I went through a lot of iterations to determine what I wanted to actually capture, and the SQL table that captures it is in the shape below. However, I built a suite of views to make the information even more relevant.

The key things for most people are the classics; FirstName/LastNname/EmailAddress and whether they are locked out, disabled or password expired. I’ve included a lot of values that we actually use regularly;

I’ve got a companion process that updates information in Active directory, so that if we needed to say, update everyone’s address or contact phone numbers, or Department, I can use Set-ADUser and update those values from my SQL Table. If that seems relevant, I’ll add a companion article to this one. It can be really scary updating Active Directory in monster batches though, a lot of people don’t have that level of comfort.

Since this information is not related to one specific application, I put the tables and views related to this in a separate database, DBA_Utilities; when you install the two scripts, you will need to create the objects in the right database, and change just two parameters the PowerShell script

###################################################################################################
#SQL Connection Setup
###################################################################################################
$SQLServer = "localhost";
$SQLDBName = "DBA_Utilities";

ActiveDirectoryTablesAndViews Details

The tables here are what I felt we needed at the time; you can strip out or add columns, but you’ll also need to strip out or add parameters and columns in the matching PowerShell script. All in all, it’s a robust collection of items to start your baseline with.

There’s a few views I created that help gather the data together for convenience, and they are not mandatory or anything, just some suggestions on how to visualize the data a little better.

{Placeholder for FullScript ActiveDirectoryTablesAndViews.sql}

FootPrint?

The PowerShell below gets All AD Users every time, I haven’t bothered to look into trying to get incremental changes; my local domain environment only has ~3000 ADUsers, many of them past employees, so I didn’t see a need to streamline the amount; if you worked for a big company, you might want to look at tuning it to get only new records. Here’s the core of the Get-ADUsers script; get the users in memory, and loop through each, calling an update-insert SQL command to update/insert the data into the table.

--#################################################################################################
-- Three core tables to hold, Users,Groups and Group Members
--#################################################################################################
IF OBJECT_ID('[dbo].[GetActiveDirectoryUsers]') IS NOT NULL 
DROP TABLE [dbo].[GetActiveDirectoryUsers] 
GO
CREATE TABLE [dbo].[GetActiveDirectoryUsers] ( 
[ID]                    INT              IDENTITY(1,1)   NOT NULL,
[CanonicalName]         VARCHAR(128)                     NOT NULL,
[DomainName] AS LEFT([CanonicalName],CHARINDEX('/',[CanonicalName])-1),
[sAMAccountName]        VARCHAR(128)                     NOT NULL,
[OperationalUnit]       VARCHAR(128)                         NULL,
[FirstName]             VARCHAR(128)                         NULL,
[LastName]              VARCHAR(128)                         NULL,
[DisplayName]           VARCHAR(128)                         NULL,
                 VARCHAR(128)                         NULL,
[EmailAddress]          VARCHAR(128)                         NULL,
[ImpliedAcount]         AS (case when charindex('@',[EmailAddress])>(0) AND charindex('.',[EmailAddress])>charindex('@',[EmailAddress]) then (substring([EmailAddress],charindex('@',[EmailAddress])+(1),(charindex('.',[EmailAddress])-charindex('@',[EmailAddress]))-(1))+'\')+substring([EmailAddress],(1),charindex('@',[EmailAddress])-(1)) else '' end) PERSISTED,
[StreetAddress]         VARCHAR(128)                         NULL,
[City]                  VARCHAR(128)                         NULL,
[State]                 VARCHAR(128)                         NULL,
[PostalCode]            VARCHAR(128)                         NULL,
[HomePhone]             VARCHAR(128)                         NULL,
[MobilePhone]           VARCHAR(128)                         NULL,
[OfficePhone]           VARCHAR(128)                         NULL,
[Fax]                   VARCHAR(128)                         NULL,
[Company]               VARCHAR(128)                         NULL,
[Organization]          VARCHAR(128)                         NULL,
[Department]            VARCHAR(128)                         NULL,
[Title]                 VARCHAR(128)                         NULL,
[Description]           VARCHAR(128)                         NULL,
[Office]                VARCHAR(128)                         NULL,
[extensionAttribute1]   VARCHAR(128)                         NULL,
[extensionAttribute2]   VARCHAR(128)                         NULL,
[extensionAttribute3]   VARCHAR(128)                         NULL,
[extensionAttribute4]   VARCHAR(128)                         NULL,
[extensionAttribute5]   VARCHAR(128)                         NULL,
[AccountExpires]        VARCHAR(128)                         NULL,
[AccountIsEnabled]      VARCHAR(128)                         NULL,
[PasswordLastSet]       VARCHAR(128)                         NULL,
[PasswordAge]           AS (case when isdate([PasswordLastSet])=(1) then datediff(day,[PasswordLastSet],getdate()) else (0) end),
[PasswordExpires]       VARCHAR(128)                         NULL,
[PasswordNeverExpires]  VARCHAR(128)                         NULL,
[PasswordIsExpired]     VARCHAR(128)                         NULL,
[LastLogonTimestamp]    VARCHAR(128)                         NULL,
[CreatedDate]           DATETIME                             NULL,
[DWCreatedDate]         DATETIME                             NULL  CONSTRAINT [DF__GetActiveDirectoryUsers__DWCreatedDate] DEFAULT (getdate()),
[DWUpdatedDate]         DATETIME                             NULL,
CONSTRAINT   [PK__GetActiveDirectoryUsers_sAMAccountName]  PRIMARY KEY CLUSTERED    ([CanonicalName],[sAMAccountName] asc))
IF OBJECT_ID('[dbo].[GetActiveDirectoryGroups]') IS NOT NULL 
DROP TABLE [dbo].[GetActiveDirectoryGroups] 
GO
CREATE TABLE [dbo].[GetActiveDirectoryGroups] ( 
[ID]                 INT              IDENTITY(1,1)   NOT NULL,
[CanonicalName]      VARCHAR(128)                     NOT NULL,
[DomainName] AS LEFT([CanonicalName],CHARINDEX('/',[CanonicalName])-1),
[SamAccountName]     VARCHAR(128)                     NOT NULL,
[DisplayName]        VARCHAR(128)                         NULL,
[Description]        VARCHAR(128)                         NULL,
[DistinguishedName]  VARCHAR(128)                         NULL,
[GroupCategory]      VARCHAR(128)                         NULL,
[GroupScope]         VARCHAR(128)                         NULL,
[CreatedDate]        DATETIME                             NULL,
[DWCreatedDate]      DATETIME                             NULL  CONSTRAINT [DF__GetActiveDirectoryGroups__DWCreatedDate] DEFAULT (getdate()),
[DWUpdatedDate]      DATETIME                             NULL,
CONSTRAINT   [PK__GetActiveDirectoryGroups_SamAccountName]  PRIMARY KEY CLUSTERED    ([CanonicalName],[SamAccountName] asc))

IF OBJECT_ID('[dbo].[GetActiveDirectoryGroupMembers]') IS NOT NULL 
DROP TABLE [dbo].[GetActiveDirectoryGroupMembers] 
GO
CREATE TABLE [dbo].[GetActiveDirectoryGroupMembers] ( 
[ID]                   INT              IDENTITY(1,1)   NOT NULL,
[GroupCanonicalName]   VARCHAR(128)                     NOT NULL,
[GroupSamAccountName]  VARCHAR(128)                     NOT NULL,
[DomainName] AS LEFT([GroupCanonicalName],CHARINDEX('/',[GroupCanonicalName])-1),
[SamAccountName]       VARCHAR(128)                     NOT NULL,
[ObjectClass]          VARCHAR(128)                     NOT NULL,
[DWCreatedDate]        DATETIME                             NULL  CONSTRAINT [DF__GetActiveDirectoryGroupMembers__DWCreatedDate] DEFAULT (getdate()),
[DWUpdatedDate]        DATETIME                             NULL,
[DWIsDeleted]          BIT                                  NULL  CONSTRAINT [DF__GetActiveDirectoryGroupMembers__DWIsDeleted] DEFAULT ((0)),
[DWDeletedDate]        DATETIME                             NULL,
CONSTRAINT   [UQ_GetActiveDirectoryGroupMembers_GroupUserObject]  UNIQUE      CLUSTERED    ([GroupCanonicalName] asc,[GroupSamAccountName] asc,[SamAccountName] asc, [ObjectClass] asc))
--#################################################################################################
-- Some Handy Views to make the data a wee bit more accessible.
--#################################################################################################
IF OBJECT_ID('[dbo].[vwActiveDirectoryUsers]') IS NOT NULL 
DROP  VIEW      [dbo].[vwActiveDirectoryUsers] 
GO
--#################################################################################################
-- vwActiveDirectoryUsers, subset of data, underlying table populated by a powershell script and Scheduled job three times a week
--#################################################################################################
--SELECT * FROM vwActiveDirectoryUsers
CREATE View vwActiveDirectoryUsers
AS
SELECT  CASE
           WHEN AD.AccountIsEnabled = 'False' 
           THEN 1
           WHEN AD.PasswordisExpired = 'True'
            AND AD.PasswordNeverExpires = 'False'
           THEN 1
           ELSE 0
         END AS [IsDisabledOrLockedOut],
         CASE
           WHEN ( AD.PasswordisExpired = 'False'
                  AND AD.AccountIsEnabled = 'True' )
                AND IsDate(AD.LastLogonTimestamp) = 1
                AND CONVERT(DATETIME, AD.LastLogonTimestamp) >= Dateadd(dd, -30, Getdate()) THEN 1
           ELSE 0
         END AS [ActiveLast30Days],
AD.ID AS ID,
LEFT(AD.CanonicalName,CHARINDEX('/',CanonicalName)-1) As DomainName,
AD.FirstName,
AD.LastName,
AD.DisplayName AS Name,
AD.EmailAddress AS email,
AD.sAMAccountName,
AD.ImpliedAcount,
AD.AccountExpires,
AD.PasswordLastSet,
AD.PasswordAge,
AD.PasswordExpires,
AD.PasswordNeverExpires,
AD.PasswordIsExpired,
'' AS PasswordStatus,
AD.AccountIsEnabled,
AD.LastLogonTimestamp,
AD.DWCreatedDate,
AD.DWUpdatedDate
 FROM GetActiveDirectoryUsers AD
GO
IF OBJECT_ID('[dbo].[vwActiveDirectoryGroupMembers]') IS NOT NULL 
DROP  VIEW      [dbo].[vwActiveDirectoryGroupMembers] 
GO
--#################################################################################################
-- vwADGroupMembers, underlying table populated  by a powershell script and Scheduled job once a month
--#################################################################################################
CREATE VIEW vwActiveDirectoryGroupMembers
AS
select
LEFT(g.CanonicalName,CHARINDEX('/',g.CanonicalName)-1) As GroupDomainName,
g.SamAccountName As GroupSamAccountName,
g.DisplayName AS GroupDisplayName,
u.*
FROM GetActiveDirectoryGroupMembers gm
INNER JOIN GetActiveDirectoryGroups g on gm.GroupSamAccountName = g.SamAccountName 
AND gm.DomainName = g.DomainName
INNER JOIN  GetActiveDirectoryUsers u on gm.SamAccountName = u.sAMAccountName
AND  gm.DomainName = u.DomainName
GO
IF OBJECT_ID('[dbo].[vwGetActiveDirectoryUsers]') IS NOT NULL 
DROP  VIEW      [dbo].[vwGetActiveDirectoryUsers] 
GO
--#################################################################################################
-- vwGetActiveDirectoryUsers, full column list,underlying table populated by a powershell script and Scheduled job three times a week
--#################################################################################################
CREATE VIEW vwGetActiveDirectoryUsers
AS
  SELECT CASE
           WHEN ( AD.PasswordisExpired = 'True'
                  AND AD.AccountIsEnabled = 'True' )
                 OR AD.AccountIsEnabled = 'False' THEN 1
           ELSE 0
         END AS [IsDisabledOrLockedOut],
         CASE
           WHEN ( AD.PasswordisExpired = 'False'
                  AND AD.AccountIsEnabled = 'True' )
                AND IsDate(AD.LastLogonTimestamp) = 1
                AND CONVERT(DATETIME, AD.LastLogonTimestamp) >= Dateadd(dd, -30, Getdate()) THEN 1
           ELSE 0
         END AS [ActiveLast30Days],
         AD.[ID],
         AD.[CanonicalName],
         AD.[DomainName],
         AD.[sAMAccountName],
         AD.[OperationalUnit],
         AD.[FirstName],
         AD.[LastName],
         AD.[DisplayName],
         AD.,
         AD.[EmailAddress],
         AD.[ImpliedAcount],
         AD.[StreetAddress],
         AD.[City],
         AD.[State],
         AD.[PostalCode],
         AD.[HomePhone],
         AD.[MobilePhone],
         AD.[OfficePhone],
         AD.[Fax],
         AD.[Company],
         AD.[Organization],
         AD.[Department],
         AD.[Title],
         AD.[Description],
         AD.[Office],
         AD.[extensionAttribute1],
         AD.[extensionAttribute2],
         AD.[extensionAttribute3],
         AD.[extensionAttribute4],
         AD.[extensionAttribute5],
         AD.[AccountExpires],
         AD.[AccountIsEnabled],
         AD.[PasswordLastSet],
         AD.[PasswordAge],
         AD.[PasswordExpires],
         AD.[PasswordNeverExpires],
         AD.[PasswordIsExpired],
         AD.[LastLogonTimestamp],
         AD.[CreatedDate],
         AD.[DWCreatedDate],
         AD.[DWUpdatedDate]
  FROM   GetActiveDirectoryUsers AD 
GO
IF OBJECT_ID('[dbo].[vwActiveDirectorySummary]') IS NOT NULL 
DROP  VIEW      [dbo].[vwActiveDirectorySummary] 
GO
--#################################################################################################
-- vwActiveDirectorySummary, rollup of data, underlying table populated by a powershell script and Scheduled job three times a week
--#################################################################################################
CREATE VIEW vwActiveDirectorySummary
AS
SELECT 
ad.DomainName,
COUNT(*) AS TotalADUsers,
 SUM(CASE
           WHEN ( AD.PasswordisExpired = 'True'
                  AND AD.AccountIsEnabled = 'True' )
                 OR AD.AccountIsEnabled = 'False' THEN 1
           ELSE 0
         END) AS [IsDisabledOrLockedOut],
SUM(CASE WHEN AD.PasswordisExpired = 'True' AND AD.AccountIsEnabled = 'False' THEN 1 ELSE 0 END) As [LockedOut],
SUM(CASE WHEN AD.AccountIsEnabled = 'True' THEN 1 ELSE 0 END) As [IsDisabled],
SUM(CASE WHEN (AD.PasswordisExpired = 'False' AND AD.AccountIsEnabled = 'False') THEN 1 ELSE 0 END) As [NotDisabledOrLockedOut],
SUM(CASE WHEN (AD.PasswordisExpired = 'False' AND AD.AccountIsEnabled = 'False') AND IsDate(AD.LastLogonTimestamp) = 0 THEN 1 ELSE 0 END) As [NeverLoggedIn],
SUM(CASE WHEN (AD.PasswordisExpired = 'False' AND AD.AccountIsEnabled = 'False') AND IsDate(AD.LastLogonTimestamp) = 1 AND CONVERT(datetime,AD.LastLogonTimestamp) >= dateadd(dd,-30,getdate()) THEN 1 ELSE 0 END) As [ActiveLast30Days],
SUM(CASE WHEN (AD.PasswordisExpired = 'False' AND AD.AccountIsEnabled = 'False') AND IsDate(AD.LastLogonTimestamp) = 1 AND CONVERT(datetime,AD.LastLogonTimestamp) <= dateadd(dd,-30,getdate()) AND AD.PasswordisExpired = 'False'AND AD.AccountIsEnabled = 'False' THEN 1 ELSE 0 END) As [NotActiveInMoreThan30Days],
'' AS Filler
FROM [dbo].[GetActiveDirectoryUsers] AD
GROUP BY ad.DomainName
GO

Timewise, it takes less than three minutes to run the script for users, but the script for Groups and Group members often takes more than ten minutes; it’s grabbing a lot more records and making a lot more calls.

In a real life scenario, for our combined domain, here’s some rowcounts for my tables, which contains two domain forests, Each with roughly 3K users ; you can see the Group Members is roughly 10X the User Count, because users exist in multiple groups.

SchemaNameObjectNameTheCountcmd
dboGetActiveDirectoryGroupMembers62054SELECT * FROM [dbo].[GetActiveDirectoryGroupMembers]
dboGetActiveDirectoryUsers6602SELECT * FROM [dbo].[GetActiveDirectoryUsers]
dboGetActiveDirectoryGroups2464SELECT * FROM [dbo].[GetActiveDirectoryGroups]

GetActiveDirectoryUsers Details

The logic in the script below is to basically call a parameterized upsert command for each row returned from the Get-ADUser Call. A Get-String function simply returns empty strings for Nothing/NULL  values. While techically a RBAR kind of action, It made sense to me at the time to do it this way, so I won't second guess myself and try to build something faster for now.

#GetActiveDirectoryUsers
Import-Module ActiveDirectory
###################################################################################################
# PowerShell to grab all active directory users incrementally
#inserting or updating them into a SQL table.
###################################################################################################
function Get-String ($obj){
    if ([string]::IsNullOrEmpty($obj))
    {
        return ""
    }
    else
    {
        return $obj.ToString();
    }
}
###################################################################################################
#SQL Connection Setup
###################################################################################################
$SQLServer = "localhost";
$SQLDBName = "DBA_Utilities";
$SqlQuery = @"
UPDATE [DBA_Utilities].[dbo].[GetActiveDirectoryUsers]
SET 
[OperationalUnit]     = @OperationalUnit,
[FirstName]           = @FirstName,
[LastName]            = @LastName,
[DisplayName]         = @DisplayName,
               = @email,
[EmailAddress]        = @EmailAddress,
[StreetAddress]       = @StreetAddress,
[City]                = @City,
[State]               = @State,
[PostalCode]          = @PostalCode,
[HomePhone]           = @HomePhone,
[MobilePhone]         = @MobilePhone,
[OfficePhone]         = @OfficePhone,
[Fax]                 = @Fax,
[Company]             = @Company,
[Organization]        = @Organization,
[Department]          = @Department,
[Title]               = @Title,
[Description]         = @Description,
[Office]              = @Office,
[extensionAttribute1] = @extensionAttribute1,
[extensionAttribute2] = @extensionAttribute2,
[extensionAttribute3] = @extensionAttribute3,
[extensionAttribute4] = @extensionAttribute4,
[extensionAttribute5] = @extensionAttribute5,
[AccountExpires]      = @AccountExpires,
[AccountIsEnabled]    = @AccountIsEnabled,
[PasswordLastSet]     = @PasswordLastSet,
[PasswordExpires]     = @PasswordExpires,
[PasswordNeverExpires]= @PasswordNeverExpires,
[PasswordIsExpired]   = @PasswordIsExpired,
[LastLogonTimestamp]  = @LastLogonTimestamp,
[CreatedDate]         = @CreatedDate,
[DWUpdatedDate]       = getdate()
WHERE[sAMAccountName] = @sAMAccountName 
AND [CanonicalName]   = @CanonicalName
IF @@ROWCOUNT = 0
BEGIN
  INSERT INTO [DBA_Utilities].[dbo].[GetActiveDirectoryUsers] 
          (CanonicalName,[sAMAccountName],[OperationalUnit],[FirstName],[LastName],[DisplayName],,[EmailAddress],[StreetAddress],[City],[State],[PostalCode],[HomePhone],[MobilePhone],[OfficePhone],[Office],[Fax],[Company],[Organization],[Department],[Title],[Description],[extensionAttribute1],[extensionAttribute2],[extensionAttribute3],[extensionAttribute4],[extensionAttribute5],[AccountExpires],[AccountIsEnabled],[PasswordLastSet],[PasswordExpires],[PasswordNeverExpires],[PasswordIsExpired],[LastLogonTimestamp])
    SELECT @CanonicalName,@sAMAccountName ,@OperationalUnit ,@FirstName ,@LastName ,@DisplayName ,@email ,@EmailAddress ,@StreetAddress ,@City ,@State ,@PostalCode ,@HomePhone ,@MobilePhone ,@OfficePhone ,@Office ,@Fax ,@Company ,@Organization ,@Department ,@Title ,@Description ,@extensionAttribute1 ,@extensionAttribute2 ,@extensionAttribute3 ,@extensionAttribute4 ,@extensionAttribute5 ,@AccountExpires ,@AccountIsEnabled ,@PasswordLastSet ,@PasswordExpires ,@PasswordNeverExpires ,@PasswordIsExpired ,@LastLogonTimestamp
END
"@
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True";
$SqlConnection.Open();
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.Connection = $SqlConnection;
$SqlCmd.CommandType = [System.Data.CommandType]::Text ;
##$SqlCmd.CommandText = "SELECT COUNT(*) FROM [DBA_Utilities].[dbo].[ActiveDirectoryUsers]";
##$RecordCount = $SqlCmd.ExecuteScalar();
$SqlCmd.CommandText = $SqlQuery;
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@CanonicalName",       [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@sAMAccountName",      [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@OperationalUnit",     [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@FirstName",           [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@LastName",            [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@DisplayName",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@email",               [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@EmailAddress",        [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@StreetAddress",       [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@City",                [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@State",               [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@PostalCode",          [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@HomePhone",           [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@MobilePhone",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@OfficePhone",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Fax",                 [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Company",             [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Organization",        [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Department",          [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Title",               [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Description",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Office",              [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@extensionAttribute1", [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@extensionAttribute2", [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@extensionAttribute3", [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@extensionAttribute4", [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@extensionAttribute5", [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@AccountExpires",      [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@AccountIsEnabled",    [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@PasswordLastSet",     [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@PasswordExpires",     [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@PasswordNeverExpires",[Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@PasswordIsExpired",   [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@LastLogonTimestamp",  [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@CreatedDate",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
###################################################################################################
#Get the Results From AD
###################################################################################################
#$Results = Get-ADUser -Filter * -Server "BigBang.com" -ResultSetSize 10  -Properties * | select -property sAMAccountName,ou,
$Results = Get-ADUser -Filter *  -Properties * | select -property CanonicalName,sAMAccountName,ou,
  GivenName,SurName,DisplayName,email,emailaddress,
  StreetAddress,City,State,PostalCode,
  HomePhone,MobilePhone,OfficePhone,Fax,
  Company,Organization,Department,Title,Description,Office,
  extensionAttribute1,extensionAttribute2,extensionAttribute3,extensionAttribute4,extensionAttribute5,
  @{Name='AccountExpires';Expression={[DATETIME]::fromFileTime($_.accountExpires)}},Enabled,PasswordLastSet,
  @{n="PasswordExpirationDate";e={$_.PasswordLastSet.AddDays($maxPasswordAge)}},PasswordNeverExpires,PasswordExpired,
  LastLogonDate,whenCreated
   foreach ($item in $Results) {
    $SqlCmd.Parameters[0].Value  = Get-String($item.CanonicalName)
    $SqlCmd.Parameters[1].Value  = Get-String($item.sAMAccountName)
    $SqlCmd.Parameters[2].Value  = Get-String($item.ou)
    $SqlCmd.Parameters[3].Value  = Get-String($item.GivenName)
    $SqlCmd.Parameters[4].Value  = Get-String($item.SurName)
    $SqlCmd.Parameters[5].Value  = Get-String($item.DisplayName)
    $SqlCmd.Parameters[6].Value  = Get-String($item.email)
    $SqlCmd.Parameters[7].Value  = Get-String($item.emailaddress)
    $SqlCmd.Parameters[8].Value  = Get-String($item.StreetAddress)
    $SqlCmd.Parameters[9].Value  = Get-String($item.City)
    $SqlCmd.Parameters[10].Value = Get-String($item.State)
    $SqlCmd.Parameters[11].Value = Get-String($item.PostalCode)
    $SqlCmd.Parameters[12].Value = Get-String($item.HomePhone)
    $SqlCmd.Parameters[13].Value = Get-String($item.MobilePhone)
    $SqlCmd.Parameters[14].Value = Get-String($item.OfficePhone)
    $SqlCmd.Parameters[15].Value = Get-String($item.Fax)
    $SqlCmd.Parameters[16].Value = Get-String($item.Company)
    $SqlCmd.Parameters[17].Value = Get-String($item.Organization)
    $SqlCmd.Parameters[18].Value = Get-String($item.Department)
    $SqlCmd.Parameters[19].Value = Get-String($item.Title)
    $SqlCmd.Parameters[20].Value = Get-String($item.Description)
    $SqlCmd.Parameters[21].Value = Get-String($item.Office)
    $SqlCmd.Parameters[22].Value = Get-String($item.extensionAttribute1)
    $SqlCmd.Parameters[23].Value = Get-String($item.extensionAttribute2)
    $SqlCmd.Parameters[24].Value = Get-String($item.extensionAttribute3)
    $SqlCmd.Parameters[25].Value = Get-String($item.extensionAttribute4)
    $SqlCmd.Parameters[26].Value = Get-String($item.extensionAttribute5)
    $SqlCmd.Parameters[27].Value = Get-String($item.AccountExpires)
    $SqlCmd.Parameters[28].Value = Get-String($item.Enabled)
    $SqlCmd.Parameters[29].Value = Get-String($item.PasswordLastSet)
    $SqlCmd.Parameters[30].Value = Get-String($item.PasswordExpirationDate)
    $SqlCmd.Parameters[31].Value = Get-String($item.PasswordNeverExpires)
    $SqlCmd.Parameters[32].Value = Get-String($item.PasswordExpired)
    $SqlCmd.Parameters[33].Value = Get-String($item.LastLogonDate)
    $SqlCmd.Parameters[34].Value = Get-String($item.whenCreated)
$SqlCmd.ExecuteNonQuery();
    }
        if ($SqlConnection.State -eq "Open") {$SqlConnection.Close()}

GetActiveDirectoryGroups Details

The logic in the script below is similar to the first example : call a parameterized upsert command for each row returned from the Get-ADGroup Call., but for each group, we also Call Get-ADGroupMembers and upsert those into a third table.

So this one proc is populating two tables for us. The Get-ADGroupMembers call gives us four different ObjectClass values; user/group/computer or blank values.

It’s pretty handy to now have a list of all known computers in active directory, as it might be the basis for scanning your computers ofr services, putting them in workstation vs server logical groups, etc.

select *  from [GetActiveDirectoryGroupMembers] where objectclass = 'computer'

{Placeholder for FullScript Get-ActiveDirectoryGroups}

#GetActiveDirectoryGroups
Import-Module ActiveDirectory
#Get-ADGroup -Filter {samaccountname -eq "Citrix_Test_Users" -ResultSetSize 1  -Properties * | SELECT SamAccountName,Name,Description,DistinguishedName,CanonicalName,GroupCategory,GroupScope,whenCreated
#Get-ADGroupMember -Identity "Citrix_Test_Users" | SELECT -property SamAccountName
#Get-ADGroupMember -Filter {samaccountname -eq "Citrix_Test_Users" -and objectClass -eq "user"}  | SELECT -property SamAccountName
#Get-ADGroupMember -Filter {samaccountname -eq "Citrix_Test_Users"}  | SELECT -property SamAccountName
#GetActiveDirectoryUsers
###################################################################################################
# PowerShell to grab all active directory groups incrementally
#inserting or updating them into a SQL table.
###################################################################################################
function Get-String ($obj){
    if ([string]::IsNullOrEmpty($obj))
    {
        return ""
    }
    else
    {
        return $obj.ToString();
    }
}
###################################################################################################
#SQL Connection Setup
###################################################################################################
$SQLServer = "localhost";
$SQLDBName = "DBA_Utilities";
$SqlQuery = @"
UPDATE [DBA_Utilities].[dbo].[GetActiveDirectoryGroups]
SET 
[DisplayName]         = @DisplayName,
[Description]         = @Description,
[DistinguishedName]   = @DistinguishedName,
[CanonicalName]       = @CanonicalName,
[GroupCategory]       = @GroupCategory,
[GroupScope]          = @GroupScope,
[CreatedDate]         = @CreatedDate,
[DWUpdatedDate]       = getdate()
WHERE sAMAccountName = @sAMAccountName
IF @@ROWCOUNT = 0
BEGIN
  INSERT INTO [DBA_Utilities].[dbo].[GetActiveDirectoryGroups] 
          ([sAMAccountName],[DisplayName],[Description],[DistinguishedName],[CanonicalName],[GroupCategory],[GroupScope],[CreatedDate])
    SELECT @sAMAccountName ,@DisplayName ,@Description ,@DistinguishedName ,@CanonicalName ,@GroupCategory ,@GroupScope ,@CreatedDate 
END
"@
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection;
$SqlConnection.ConnectionString = "Server = $SQLServer; Database = $SQLDBName; Integrated Security = True";
$SqlConnection.Open();
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand;
$SqlCmd.Connection = $SqlConnection;
$SqlCmd.CommandType = [System.Data.CommandType]::Text ;
##$SqlCmd.CommandText = "SELECT COUNT(*) FROM [DBA_Utilities].[dbo].[ActiveDirectoryGroups]";
##$RecordCount = $SqlCmd.ExecuteScalar();
$SqlCmd.CommandText = $SqlQuery;
 
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@sAMAccountName",      [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@DisplayName",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@Description",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@DistinguishedName",   [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@CanonicalName",       [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@GroupCategory",       [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@GroupScope",          [Data.SQLDBType]::VarChar, 128))) | Out-Null
$SqlCmd.Parameters.Add((New-Object Data.SqlClient.SqlParameter("@CreatedDate",         [Data.SQLDBType]::VarChar, 128))) | Out-Null
###################################################################################################
#Group Members Setup
###################################################################################################
[string]$MembersQuery = @"
  DECLARE @GCanonicalName  varchar(128),
          @GSamAccountName varchar(128),
          @SamAccountName  varchar(128),
          @ObjectClass     varchar(128);
          
  SELECT  @GCanonicalName  = 'param1',
          @GSamAccountName = 'param2',
          @SamAccountName  = 'param3',
          @ObjectClass     = 'param4';
  
  UPDATE [DBA_Utilities].[dbo].[GetActiveDirectoryGroupMembers]
  SET DWUpdatedDate = getdate()
  WHERE [GroupCanonicalName]  = @GCanonicalName
    AND [GroupSamAccountName] = @GSamAccountName 
    AND [SamAccountName]      = @SamAccountName
    AND [ObjectClass]         = @ObjectClass
    
  IF @@ROWCOUNT = 0
    BEGIN                        
      INSERT INTO [DBA_Utilities].[dbo].[GetActiveDirectoryGroupMembers] 
              ([GroupCanonicalName],[GroupSamAccountName],[SamAccountName],[ObjectClass])
        SELECT @GCanonicalName,@GSamAccountName ,@SamAccountName,@ObjectClass  
          WHERE NOT EXISTS(SELECT * 
                           FROM [DBA_Utilities].[dbo].[GetActiveDirectoryGroupMembers] 
                           WHERE [GroupSamAccountName] = @GSamAccountName 
                             AND [SamAccountName]      = @SamAccountName
                             AND [ObjectClass]         = @ObjectClass)
    END --IF                         
"@
$MembersCmd = New-Object System.Data.SqlClient.SqlCommand;
$MembersCmd.Connection = $SqlConnection;
$MembersCmd.CommandType = [System.Data.CommandType]::Text ;

###################################################################################################
#Get the Results From AD
###################################################################################################
#$Results = Get-ADGroup -Filter * -Server "BigBang.com" -ResultSetSize 10  -Properties * | SELECT SamAccountName,Name,Description,DistinguishedName,CanonicalName,GroupCategory,GroupScope,whenCreated
$Results = Get-ADGroup -Filter *  -Properties * | select -property SamAccountName,Name,Description,DistinguishedName,CanonicalName,GroupCategory,GroupScope,whenCreated
   foreach ($item in $Results) {
    $SqlCmd.Parameters[0].Value  = Get-String($item.SamAccountName)
    $SqlCmd.Parameters[1].Value  = Get-String($item.Name)
    $SqlCmd.Parameters[2].Value  = Get-String($item.Description)
    $SqlCmd.Parameters[3].Value  = Get-String($item.DistinguishedName)
    $SqlCmd.Parameters[4].Value  = Get-String($item.CanonicalName)
    $SqlCmd.Parameters[5].Value  = Get-String($item.GroupCategory)
    $SqlCmd.Parameters[6].Value  = Get-String($item.GroupScope)
    $SqlCmd.Parameters[7].Value  = Get-String($item.whenCreated)
$SqlCmd.ExecuteNonQuery() | Out-Null;;
    #now the details! members info!
    #$MemberResults = Get-ADGroupMember -Filter {Samaccountname -eq $item.SamAccountName -and objectClass -eq "user"} | SELECT -property SamAccountName
    [string]$can = $item.CanonicalName.ToString()
    [string]$grp = $item.SamAccountName.ToString()
    #$MemberResults = Get-ADGroupMember -Server "BigBang.com" -Identity $grp | SELECT -property SamAccountName,objectClass
    $MemberResults = Get-ADGroupMember -Identity $grp | SELECT -property SamAccountName,objectClass
    
     #write-host $item.SamAccountName
     foreach ($MemberItem in $MemberResults) {
        [string]$s = Get-String($MemberItem.SamAccountName)
        [string]$o = Get-String($MemberItem.objectClass);
         #write-host $grp '|' $s '|' $o
         
         [string] $sq = $MembersQuery;
         $sq = $sq -Replace "param1",$can.Replace("'","''")
         $sq = $sq -Replace "param2",$grp.Replace("'","''")
         $sq = $sq -Replace "param3",$s.Replace("'","''")
         $sq = $sq -Replace "param4",$o.Replace("'","''")
         #write-host $sq
        $MembersCmd.CommandText = $sq; 
        $MembersCmd.ExecuteNonQuery() | Out-Null;
        }
    }
        if ($SqlConnection.State -eq "Open") {$SqlConnection.Close()}

Run This Regularly or not?

I’ve included a scripted Job as part of the article as well. However, running things that Get AD info requires a domain user, so you need all that that would entail: a Credential, an Operator, and operator permissions.

I’ve created a scheduled job that scans for users M-W-F, but I end up refreshing Groups and Group members rarely, if ever. I’ve disabled the job step in my case, since I rarely need to compare group information. I might run it manually once a month or so, but my own needs tend towards reviewing end users.

So What Can I do with it?

So you’ve got some nifty tables full of this information, but if you don’t actually use it, it has no real value. Here’s just some of the things I do with it.

  1. Remove SQL users that are disabled. I create a script that does an IF EXISTS() and drops users and logins. Pasting that into a Central Management Server multi server query, and I cleanup tons of servers at a time.
  2. Generate and verify the list of users who should have access to various resources based on the groups they belong to.
  3. Adding AD users quickly to our application via insert…from syntax.
  4. Finding users who exist in recursive groups.
  5. Inventorying Servers and workstations.
  6. Providing spreadsheets to be used for Updating Active Directory; we have had several initiatives to update phone number and addresses and custom string values; I provide a spreadsheet, and then generate Set-ADUser commands to update AD, which in turn gets pulled back into my tables.

I hope you find this suite of tools for your toolbox helpful; It’s one of those Secret Sauce implementations a lot of folks might have developed independently, but are afraid to share, since they put a lot of time into it. I’ve been in countless meetings where someone says “how many people in active directory? How many active in the last 30 days? how many are just active period?”; being able to pull that info up in 30 secodns makes you look extremely competent.

Please join the discussion and give some feedback! Everything we add makes the SQL community a little better.

TLDR:

  1. Run the ActiveDirectoryTablesAndViews.sql in a database named DBA_Utilities(or the db of your choice) to create the Tables And Views.
  2. Modify the two scripts GetActiveDirectoryUsers.ps1  And GetActiveDirectoryGroups.ps1 to point to the right server and database, if it’s not localhost and DBA_Utilities.
  3. Run each Of the two PowerShell Scripts manually from either a PowerShell prompt, a PowerShellIDE, or a commandline call .
  4. Optional SQL Job is going to require a Credential and Operator, that is an AD User:
  5. Find the CredentialHelper.sql script, so that you can modify it to create the Credential, Operator, and permissions to the PowerShell and CmdExec subsystem.
  6. Copy both PowerShell files to a location on the server, ie L:\PowerShell\
  7. Modify the Script SQLJob_ETL_GetActiveDirectoryData.sql in two places to have the filepath you decided on in Step 3. Modify the Operator if you didn’t stick with “TaskRunner”, and run it to create the Job.
  8. Run the Job manually, and review the contents of the new tables and views.

Resources

Rate

4.92 (25)

You rated this post out of 5. Change rating

Share

Share

Rate

4.92 (25)

You rated this post out of 5. Change rating