Monitor-ADGroupMemberShip.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
<#PSScriptInfo
 
.VERSION 2.0.3.0
 
.GUID 28826cd6-0760-4e00-aae6-1330e60118ee
 
.AUTHOR Francois-Xavier Cat
 
.COMPANYNAME LazyWinAdmin.Com
 
.COPYRIGHT (c) 2016 Francois-Xavier Cat. All rights reserved. Licensed under The MIT License (MIT)
 
.TAGS ActiveDirectory Group GroupMembership Monitor Report ADSI Quest
 
.LICENSEURI https://github.com/lazywinadmin/Monitor-ADGroupMembership/blob/master/LICENSE
 
.PROJECTURI https://github.com/lazywinadmin/Monitor-ADGroupMembership
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>


<#
.DESCRIPTION
    This script is monitoring group(s) in Active Directory and send an email when someone is changing the membership.
    It will also report the Change History made for this/those group(s).
 
.SYNOPSIS
    This script is monitoring group(s) in Active Directory and send an email when someone is changing the membership.
 
.PARAMETER Group
    Specify the group(s) to query in Active Directory.
    You can also specify the 'DN','GUID','SID' or the 'Name' of your group(s).
    Using 'Domain\Name' will also work.
 
.PARAMETER SearchRoot
    Specify the DN, GUID or canonical name of the domain or container to search. By default, the script searches the entire sub-tree of which SearchRoot is the topmost object (sub-tree search). This default behavior can be altered by using the SearchScope parameter.
 
.PARAMETER SearchScope
    Specify one of these parameter values
        'Base' Limits the search to the base (SearchRoot) object.
            The result contains a maximum of one object.
        'OneLevel' Searches the immediate child objects of the base (SearchRoot)
            object, excluding the base object.
        'Subtree' Searches the whole sub-tree, including the base (SearchRoot)
            object and all its child objects.
 
.PARAMETER GroupScope
    Specify the group scope of groups you want to find. Acceptable values are:
        'Global';
        'Universal';
        'DomainLocal'.
 
.PARAMETER GroupType
    Specify the group type of groups you want to find. Acceptable values are:
        'Security';
        'Distribution'.
 
.PARAMETER File
    Specify the File where the Group are listed. DN, SID, GUID, or Domain\Name of the group are accepted.
 
.PARAMETER EmailServer
    Specify the Email Server IPAddress/FQDN.
 
.PARAMETER EmailTo
    Specify the Email Address(es) of the Destination. Example: fxcat@fx.lab
 
.PARAMETER EmailFrom
    Specify the Email Address of the Sender. Example: Reporting@fx.lab
 
.PARAMETER EmailEncoding
    Specify the Body and Subject Encoding to use in the Email.
    Default is ASCII.
 
.PARAMETER Server
    Specify the Domain Controller to use.
    Aliases: DomainController, Service
 
.PARAMETER HTMLLog
    Specify if you want to save a local copy of the Report.
    It will be saved under the directory "HTML".
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -Group "FXGroup" -EmailFrom "From@Company.com" -EmailTo "To@Company.com" -EmailServer "mail.company.com"
 
    This will run the script against the group FXGROUP and send an email to To@Company.com using the address From@Company.com and the server mail.company.com.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -Group "FXGroup","FXGroup2","FXGroup3" -EmailFrom "From@Company.com" -Emailto "To@Company.com" -EmailServer "mail.company.com"
 
    This will run the script against the groups FXGROUP,FXGROUP2 and FXGROUP3 and send an email to To@Company.com using the address From@Company.com and the Server mail.company.com.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -Group "FXGroup" -EmailFrom "From@Company.com" -Emailto "To@Company.com" -EmailServer "mail.company.com" -Verbose
 
    This will run the script against the group FXGROUP and send an email to To@Company.com using the address From@Company.com and the server mail.company.com. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -Group "FXGroup" -EmailFrom "From@Company.com" -Emailto "Auditor@Company.com","Auditor2@Company.com" -EmailServer "mail.company.com" -Verbose
 
    This will run the script against the group FXGROUP and send an email to Auditor@Company.com and Auditor2@Company.com using the address From@Company.com and the server mail.company.com. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -SearchRoot 'FX.LAB/TEST/Groups' -Emailfrom Reporting@fx.lab -Emailto "Catfx@fx.lab" -EmailServer 192.168.1.10 -Verbose
 
    This will run the script against all the groups present in the CanonicalName 'FX.LAB/TEST/Groups' and send an email to catfx@fx.lab using the address Reporting@fx.lab and the server 192.168.1.10. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -file .\groupslist.txt -Emailfrom Reporting@fx.lab -Emailto "Catfx@fx.lab" -EmailServer 192.168.1.10 -Verbose
 
    This will run the script against all the groups present in the file groupslists.txt and send an email to catfx@fx.lab using the address Reporting@fx.lab and the server 192.168.1.10. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -server DC01.fx.lab -file .\groupslist.txt -Emailfrom Reporting@fx.lab -Emailto "Catfx@fx.lab" -EmailServer 192.168.1.10 -Verbose
 
    This will run the script against the Domain Controller "DC01.fx.lab" on all the groups present in the file groupslists.txt and send an email to catfx@fx.lab using the address Reporting@fx.lab and the server 192.168.1.10. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -server DC01.fx.lab:389 -file .\groupslist.txt -Emailfrom Reporting@fx.lab -Emailto "Catfx@fx.lab" -EmailServer 192.168.1.10 -Verbose
 
    This will run the script against the Domain Controller "DC01.fx.lab" (on port 389) on all the groups present in the file groupslists.txt and send an email to catfx@fx.lab using the address Reporting@fx.lab and the server 192.168.1.10. Additionally the switch Verbose is activated to show the activities of the script.
 
.EXAMPLE
    .\AD-GROUP-Monitor_MemberShip.ps1 -group "Domain Admins" -Emailfrom Reporting@fx.lab -Emailto "Catfx@fx.lab" -EmailServer 192.168.1.10 -EmailEncoding 'ASCII' -HTMLlog
 
    This will run the script against the group "Domain Admins" and send an email (using the encoding ASCII) to catfx@fx.lab using the address Reporting@fx.lab and the SMTP server 192.168.1.10. It will also save a local HTML report under the HTML Directory.
 
.INPUTS
    System.String
 
.OUTPUTS
    Email Report
 
.NOTES
    NAME: AD-GROUP-Monitor_MemberShip.ps1
    AUTHOR: Francois-Xavier Cat
    EMAIL: info@lazywinadmin.com
    WWW: www.lazywinadmin
    Twitter:@lazywinadm
 
    REQUIREMENTS:
        -Read Permission in Active Directory on the monitored groups
        -Quest Active Directory PowerShell Snapin
        -A Scheduled Task (in order to check every X seconds/minutes/hours)
 
    VERSION HISTORY:
    1.0 2012.02.01
        Initial Version
 
    1.1 2012.03.13
        CHANGE to monitor both Domain Admins and Enterprise Admins
 
    1.2 2013.09.23
        FIX issue when specifying group with domain 'DOMAIN\Group'
        CHANGE Script Format (BEGIN, PROCESS, END)
        ADD Minimal Error handling. (TRY CATCH)
 
    1.3 2013.10.05
        CHANGE in the PROCESS BLOCK, the TRY CATCH blocks and placed
         them inside the FOREACH instead of inside the TRY block
        ADD support for Verbose
        CHANGE the output file name "DOMAIN_GROUPNAME-membership.csv"
        ADD a Change History File for each group(s)
         example: "GROUPNAME-ChangesHistory-yyyyMMdd-hhmmss.csv"
        ADD more Error Handling
        ADD a HTML Report instead of plain text
        ADD HTML header
        ADD HTML header for change history
 
    1.4 2013.10.11
        CHANGE the 'Change History' filename to
         "DOMAIN_GROUPNAME-ChangesHistory-yyyyMMdd-hhmmss.csv"
        UPDATE Comments Based Help
        ADD Some Variable Parameters
         
    1.5 2013.10.13
        ADD the full Parameter Names for each Cmdlets used in this script
        ADD Alias to the Group ParameterName
         
    1.6 2013.11.21
        ADD Support for Organizational Unit (SearchRoot parameter)
        ADD Support for file input (File Parameter)
        ADD ParamaterSetNames and parameters GroupType/GroupScope/SearchScope
        REMOVE [mailaddress] type on $Emailfrom and $EmailTo to make the script available to PowerShell 2.0
        ADD Regular expression validation on $Emailfrom and $EmailTo
     
    1.7 2013.11.23
        ADD ValidateScript on File Parameter
        ADD Additional information about the Group in the Report
        CHANGE the format of the $changes output, it will now include the DateTime Property
        UPDATE Help
        ADD DisplayName Property in the report
         
    1.8 2013.11.27
        Minor syntax changes
        UPDATE Help
     
    1.8.1 2013.12.29
        Rename to AD-GROUP-Monitor_MemberShip
 
    1.8.2 2014.02.17
        Update Notes
 
    2.0 2014.05.04
        ADD Support for ActiveDirectory module (AD module is use by default)
        ADD failover to Quest AD Cmdlet if AD module is available
        RENAME GetQADGroupParams variable to ADGroupParams
 
    2.0.1 2015.01.05
        REMOVE the DisplayName property from the email
        ADD more clear details/Comments
        RENAME a couple of Verbose and Warning Messages
        FIX the DN of the group in the Summary
        FIX SearchBase/SearchRoot Parameter which was not working with AD Module
        FIX Some other minor issues
        ADD Check to validate data added to $Group is valid
        ADD Server Parameter to be able to specify a domain controller
 
    2.0.2 2015.01.14
        FIX an small issue with the $StateFile which did not contains the domain
        ADD the property Name into the final output.
        ADD Support to export the report to a HTML file (-HTMLLog) It will save
            the report under the folder HTML
        ADD Support for alternative Email Encoding: Body and Subject. Default is ASCII.
 
 
    TODO:
        -Add Switch to make the Group summary Optional (info: Description,DN,CanonicalName,SID, Scope, Type)
            -Current Member Count, Added Member count, Removed Member Count
        -Switch to Show all the Current Members (Name, Department, Role, EMail)
        -Possibility to Ignore some groups
        -Email Credential
        -Recursive Membership search
        -Switch to save a local copy of the HTML report (maybe put this by default)
#>


#requires -version 3.0

[CmdletBinding(DefaultParameterSetName = "Group")]
PARAM (
    [Parameter(ParameterSetName = "Group", Mandatory = $true, HelpMessage = "You must specify at least one Active Directory group")]
    [ValidateNotNull()]
    [Alias('DN', 'DistinguishedName', 'GUID', 'SID', 'Name')]
    [string[]]$Group,
    
    [Parameter(ParameterSetName = "OU", Mandatory = $true)]
    [Alias('SearchBase')]
    [String[]]$SearchRoot,
    
    [Parameter(ParameterSetName = "OU")]
    [ValidateSet("Base", "OneLevel", "Subtree")]
    [String]$SearchScope,
    
    [Parameter(ParameterSetName = "OU")]
    [ValidateSet("Global", "Universal", "DomainLocal")]
    [String]$GroupScope,
    
    [Parameter(ParameterSetName = "OU")]
    [ValidateSet("Security", "Distribution")]
    [String]$GroupType,
    
    [Parameter(ParameterSetName = "File", Mandatory = $true)]
    [ValidateScript({ Test-Path -Path $_ })]
    [String[]]$File,
    
    [Parameter()]
    [Alias('DomainController', 'Service')]
    $Server,
    
    [Parameter(Mandatory = $true, HelpMessage = "You must specify the Sender Email Address")]
    [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
    [String]$Emailfrom,
    
    [Parameter(Mandatory = $true, HelpMessage = "You must specify the Destination Email Address")]
    [ValidatePattern("[a-z0-9!#\$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?")]
    [String[]]$Emailto,
    
    [Parameter(Mandatory = $true, HelpMessage = "You must specify the Email Server to use (IPAddress or FQDN)")]
    [String]$EmailServer,
    
    [Parameter()]
    [ValidateSet("ASCII", "UTF8", "UTF7", "UTF32", "Unicode", "BigEndianUnicode", "Default")]
    [String]$EmailEncoding="ASCII",

    [Parameter()]
    [Switch]$HTMLLog
)
BEGIN
{
    TRY
    {
        
        # Set the Paths Variables and create the folders if not present
        $ScriptPath = (Split-Path -Path ((Get-Variable -Name MyInvocation).Value).MyCommand.Path)
        $ScriptPathOutput = $ScriptPath + "\Output"
        IF (!(Test-Path -Path $ScriptPathOutput))
        {
            Write-Verbose -Message "[BEGIN] Creating the Output Folder : $ScriptPathOutput"
            New-Item -Path $ScriptPathOutput -ItemType Directory | Out-Null
        }
        $ScriptPathChangeHistory = $ScriptPath + "\ChangeHistory"
        IF (!(Test-Path -Path $ScriptPathChangeHistory))
        {
            Write-Verbose -Message "[BEGIN] Creating the ChangeHistory Folder : $ScriptPathChangeHistory"
            New-Item -Path $ScriptPathChangeHistory -ItemType Directory | Out-Null
        }
        
        # Set the Date and Time variables format
        $DateFormat = Get-Date -Format "yyyyMMdd_HHmmss"
        #$ReportDateFormat = Get-Date -Format "yyyy\MM\dd HH:mm:ss"
        
        
        # Active Directory Module
        IF (Get-Module -Name ActiveDirectory -ListAvailable) #verify ad module is installed
        {
            Write-Verbose -Message "[BEGIN] Active Directory Module"
            # Verify Ad module is loaded
            IF (-not (Get-Module -Name ActiveDirectory -ErrorAction SilentlyContinue -ErrorVariable ErrorBEGINGetADModule))
            {
                Write-Verbose -Message "[BEGIN] Active Directory Module - Loading"
                Import-Module -Name ActiveDirectory -ErrorAction Stop -ErrorVariable ErrorBEGINAddADModule
                Write-Verbose -Message "[BEGIN] Active Directory Module - Loaded"
                $script:ADModule = $true
            }
            ELSE
            {
                Write-Verbose -Message "[BEGIN] Active Directory module seems loaded"
                $script:ADModule = $true
            }
        }
        ELSE # Else we try to load Quest Ad Cmdlets
        {
            Write-Verbose -Message "[BEGIN] Quest AD Snapin"
            # Verify Quest Active Directory Snapin is loaded
            IF (-not (Get-PSSnapin -Name Quest.ActiveRoles.ADManagement -ErrorAction Stop -ErrorVariable ErrorBEGINGetQuestAD))
            {
                Write-Verbose -Message "[BEGIN] Quest Active Directory - Loading"
                Add-PSSnapin -Name Quest.ActiveRoles.ADManagement -ErrorAction Stop -ErrorVariable ErrorBEGINAddQuestAd
                Write-Verbose -Message "[BEGIN] Quest Active Directory - Loaded"
                $script:QuestADSnappin = $true
            }
            ELSE
            {
                Write-Verbose -Message "[BEGIN] Quest AD Snapin seems loaded"
                $script:QuestADSnappin = $true
            }
        }
        
        Write-Verbose -Message "[BEGIN] Setting HTML Variables"
        # HTML Report settings
        $Report = "<p style=`"background-color:white;font-family:consolas;font-size:9pt`">" +
        "<strong>Report Time:</strong> $DateFormat <br>" +
        "<strong>Account:</strong> $env:userdomain\$($env:username.toupper()) on $($env:ComputerName.toUpper())" +
        "</p>"
        
        $Head = "<style>" +
        "BODY{background-color:white;font-family:consolas;font-size:11pt}" +
        "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse}" +
        "TH{border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color:`"#00297A`";font-color:white}" +
        "TD{border-width: 1px;padding-right: 2px;padding-left: 2px;padding-top: 0px;padding-bottom: 0px;border-style: solid;border-color: black;background-color:white}" +
        "</style>"
        $Head2 = "<style>" +
        "BODY{background-color:white;font-family:consolas;font-size:9pt;}" +
        "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}" +
        "TH{border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color:`"#C0C0C0`"}" +
        "TD{border-width: 1px;padding-right: 2px;padding-left: 2px;padding-top: 0px;padding-bottom: 0px;border-style: solid;border-color: black;background-color:white}" +
        "</style>"
        
        
    }#TRY
    CATCH
    {
        Write-Warning -Message "[BEGIN] Something went wrong"
        
        #Show last error
        #Write-Warning -Message $_.Exception.Message
        Write-Warning -Message $Error[0]
        
        # Quest AD Cmdlets Errors
        if ($ErrorBEGINGetQuestAD) { Write-Warning -Message "[BEGIN] Can't Find the Quest Active Directory Snappin" }
        if ($ErrorBEGINAddQuestAD) { Write-Warning -Message "[BEGIN] Can't Load the Quest Active Directory Snappin" }
        
        # AD module Errors
        if ($ErrorBEGINGetADmodule) { Write-Warning -Message "[BEGIN] Can't find the Active Directory module" }
        if ($ErrorBEGINAddADmodule) { Write-Warning -Message "[BEGIN] Can't load the Active Directory module" }
    }#CATCH
}#BEGIN

PROCESS
{
    TRY
    {
        
        # # # # # # # # # # # # # # # # #
        # SEARCHROOT parameter specified#
        # # # # # # # # # # # # # # # # #
        
        IF ($PSBoundParameters['SearchRoot'])
        {
            Write-Verbose -Message "[PROCESS] SearchRoot specified"
            FOREACH ($item in $SearchRoot)
            {
                # ADGroup Splatting
                $ADGroupParams = @{ }
                
                
                # ActiveDirectory Module
                IF ($ADModule)
                {
                    $ADGroupParams.SearchBase = $item
                    
                    # Server Specified
                    IF ($PSBoundParameters['Server']) { $ADGroupParams.Server = $Server}
                }
                IF ($QuestADSnappin)
                {
                    $ADGroupParams.SearchRoot = $item
                    
                    # Server Specified
                    IF ($PSBoundParameters['Server']) { $ADGroupParams.Service = $Server }
                }
                
                
                # # # # # # # # # # # # # # # # # #
                # SEARCHSCOPE Parameter specified #
                # # # # # # # # # # # # # # # # # #
                IF ($PSBoundParameters['SearchScope'])
                {
                    Write-Verbose -Message "[PROCESS] SearchScope specified"
                    $ADGroupParams.SearchScope = $SearchScope
                }
                
                
                # # # # # # # # # # # # # # # # #
                # GROUPSCOPE Parameter specified#
                # # # # # # # # # # # # # # # # #
                IF ($PSBoundParameters['GroupScope'])
                {
                    Write-Verbose -Message "[PROCESS] GroupScope specified"
                    # ActiveDirectory Module Parameter
                    IF ($ADModule) { $ADGroupParams.Filter = "GroupScope -eq `'$GroupScope`'" }
                    # Quest ActiveDirectory Snapin Parameter
                    ELSE { $ADGroupParams.GroupScope = $GroupScope }
                }
                
                
                # # # # # # # # # # # # # # # # #
                # GROUPTYPE Parameter specified #
                # # # # # # # # # # # # # # # # #
                IF ($PSBoundParameters['GroupType'])
                {
                    Write-Verbose -Message "[PROCESS] GroupType specified"
                    # ActiveDirectory Module
                    IF ($ADModule)
                    {
                        # ActiveDirectory Module Parameter
                        IF ($ADGroupParams.Filter)
                        {
                            $ADGroupParams.Filter = "$($ADGroupParams.Filter) -and GroupCategory -eq `'$GroupType`'"
                        }
                        ELSE
                        {
                            $ADGroupParams.Filter = "GroupCategory -eq '$GroupType'"
                        }
                    }
                    # Quest ActiveDirectory Snapin
                    ELSE
                    {
                        $ADGroupParams.GroupType = $GroupType
                    }
                }#IF ($PSBoundParameters['GroupType'])
                
                
                
                IF ($ADModule)
                {
                    IF (-not($ADGroupParams.filter)){$ADGroupParams.Filter = "*"}
                    
                    Write-Verbose -Message "[PROCESS] AD Module - Querying..."
                    
                    # Add the groups to the variable $Group
                    $GroupSearch = Get-ADGroup @ADGroupParams

                    if ($GroupSearch){
                        $group += $GroupSearch.Distinguishedname
                        Write-Verbose -Message "[PROCESS] OU: $item"
                    }
                }
                
                IF ($QuestADSnappin)
                {
                    Write-Verbose -Message "[PROCESS] Quest AD Snapin - Querying..."
                    # Add the groups to the variable $Group
                    $GroupSearchQuest = Get-QADGroup @ADGroupParams
                    if ($GroupSearchQuest){
                        $group += $GroupSearchQuest.DN
                        Write-Verbose -Message "[PROCESS] OU: $item"
                    }
                }
                
            }#FOREACH ($item in $OU)
        }#IF ($PSBoundParameters['SearchRoot'])
        
        
        
        
        # # # # # # # # # # # # # # #
        # FILE parameter specified #
        # # # # # # # # # # # # # # #
        
        IF ($PSBoundParameters['File'])
        {
            Write-Verbose -Message "[PROCESS] File"
            FOREACH ($item in $File)
            {
                Write-Verbose -Message "[PROCESS] Loading File: $item"
                
                $FileContent = Get-Content -Path $File
                
                if ($FileContent)
                {
                    # Add the groups to the variable $Group
                    $Group += Get-Content -Path $File
                }
                
                
                
            }#FOREACH ($item in $File)
        }#IF ($PSBoundParameters['File'])
        
        
        
        # # # # # # # # # # # # # # # # # # # # # # # # # # #
        # GROUP or SEARCHROOT or FILE parameters specified #
        # # # # # # # # # # # # # # # # # # # # # # # # # # #
        
        # This will run for any parameter set name ParameterSetName = OU, Group or File
        FOREACH ($item in $Group)
        {
            TRY
            {
                
                Write-Verbose -Message "[PROCESS] GROUP: $item... "
                
                # Splatting for the AD Group Request
                $GroupSplatting = @{ }
                $GroupSplatting.Identity = $item
                
                # Group Information
                if ($ADModule)
                {
                    Write-Verbose -Message "[PROCESS] ActiveDirectory module"
                    
                    # Add the Server if specified
                    IF ($PSBoundParameters['Server']) { $GroupSplatting.Server = $Server }
                    
                    # Look for Group
                    $GroupName = Get-ADGroup @GroupSplatting -Properties * -ErrorAction Continue -ErrorVariable ErrorProcessGetADGroup
                    $DomainName = ($GroupName.canonicalname -split '/')[0]
                    $RealGroupName = $GroupName.name
                }
                if ($QuestADSnappin)
                {
                    Write-Verbose -Message "[PROCESS] Quest ActiveDirectory Snapin"
                    
                    # Add the Server if specified
                    IF ($PSBoundParameters['Server']) { $GroupSplatting.Service = $Server }
                    
                    # Look for Group
                    $GroupName = Get-QADgroup @GroupSplatting -ErrorAction Continue -ErrorVariable ErrorProcessGetQADGroup
                    $DomainName = $($GroupName.domain.name)
                    $RealGroupName = $GroupName.name
                }
                
                # GroupName Found
                IF ($GroupName)
                {
                    
                    # Splatting for the AD Group Members Request
                    $GroupMemberSplatting = @{ }
                    $GroupMemberSplatting.Identity = $GroupName
                    
                    
                    # Get GroupName Membership
                    if ($ADModule)
                    {
                        Write-Verbose -Message "[PROCESS] GROUP: $item - Querying Membership (AD Module)"
                        
                        # Add the Server if specified
                        IF ($PSBoundParameters['Server']) { $GroupMemberSplatting.Server = $Server }
                        
                        # Look for Members
                        $Members = Get-ADGroupMember @GroupMemberSplatting -Recursive -ErrorAction Stop -ErrorVariable ErrorProcessGetADGroupMember | Select-Object -Property *,@{ Name = 'DN'; Expression = { $_.DistinguishedName } }
                    }
                    if ($QuestADSnappin)
                    {
                        Write-Verbose -Message "[PROCESS] GROUP: $item - Querying Membership (Quest AD Snapin)"
                        
                        # Add the Server if specified
                        IF ($PSBoundParameters['Server']) { $GroupMemberSplatting.Service = $Server }
                        
                        $Members = Get-QADGroupMember @GroupMemberSplatting -Indirect -ErrorAction Stop -ErrorVariable ErrorProcessGetQADGroupMember #| Select-Object -Property *,@{ Name = 'DistinguishedName'; Expression = { $_.dn } }
                    }
                    # NO MEMBERS, Add some info in $members to avoid the $null
                    # If the value is $null the compare-object won't work
                    IF (-not ($Members))
                    {
                        Write-Verbose -Message "[PROCESS] GROUP: $item is empty"
                        $Members = New-Object -TypeName PSObject -Property @{
                            Name = "No User or Group"
                            SamAccountName = "No User or Group"
                        }
                    }
                    
                    
                    # GroupName Membership File
                    # If the file doesn't exist, assume we don't have a record to refer to
                    $StateFile = "$($DomainName)_$($RealGroupName)-membership.csv"
                    IF (!(Test-Path -Path (Join-Path -Path $ScriptPathOutput -ChildPath $StateFile)))
                    {
                        Write-Verbose -Message "[PROCESS] $item - The following file did not exist: $StateFile"
                        Write-Verbose -Message "[PROCESS] $item - Exporting the current membership information into the file: $StateFile"
                        $Members | Export-csv -Path (Join-Path -Path $ScriptPathOutput -ChildPath $StateFile) -NoTypeInformation
                    }
                    ELSE
                    {
                        Write-Verbose -Message "[PROCESS] $item - The following file Exists: $StateFile"
                    }
                    
                    
                    # GroupName Membership File is compared with the current GroupName Membership
                    Write-Verbose -Message "[PROCESS] $item - Comparing Current and Before"
                    $ImportCSV = Import-Csv -Path (Join-Path -path $ScriptPathOutput -childpath $StateFile) -ErrorAction Stop -ErrorVariable ErrorProcessImportCSV
                    $Changes = Compare-Object -DifferenceObject $ImportCSV -ReferenceObject $Members -ErrorAction stop -ErrorVariable ErrorProcessCompareObject -Property Name, SamAccountName, DN |
                    Select-Object @{ Name = "DateTime"; Expression = { Get-Date -Format "yyyyMMdd-hh:mm:ss" } }, @{
                        n = 'State'; e = {
                            IF ($_.SideIndicator -eq "=>") { "Removed" }
                            ELSE { "Added" }
                        }
                    }, DisplayName, Name, SamAccountName, DN | Where-Object { $_.name -notlike "*no user or group*" }
                    Write-Verbose -Message "[PROCESS] $item - Compare Block Done !"
                    
                    <# Troubleshooting
                    Write-Verbose -Message "IMPORTCSV var"
                    $ImportCSV | fl -Property Name, SamAccountName, DN
                     
                    Write-Verbose -Message "MEMBER"
                    $Members | fl -Property Name, SamAccountName, DN
                    Write-Verbose -Message "CHANGE"
                    $Changes
                    #>

                    
                    # CHANGES FOUND !
                    If ($Changes)
                    {
                        Write-Verbose -Message "[PROCESS] $item - Some changes found"
                        $changes | Select-Object -Property DateTime, State, Name, SamAccountName, DN
                        
                        # CHANGE HISTORY
                        # Get the Past Changes History
                        Write-Verbose -Message "[PROCESS] $item - Get the change history for this group"
                        $ChangesHistoryFiles = Get-ChildItem -Path $ScriptPathChangeHistory\$($DomainName)_$($RealGroupName)-ChangeHistory.csv -ErrorAction 'SilentlyContinue'
                        Write-Verbose -Message "[PROCESS] $item - Change history files: $(($ChangesHistoryFiles|Measure-Object).Count)"
                        
                        # Process each history changes
                        IF ($ChangesHistoryFiles)
                        {
                            $infoChangeHistory = @()
                            FOREACH ($file in $ChangesHistoryFiles.FullName)
                            {
                                Write-Verbose -Message "[PROCESS] $item - Change history files - Loading $file"
                                # Import the file and show the $file creation time and its content
                                $ImportedFile = Import-Csv -Path $file -ErrorAction Stop -ErrorVariable ErrorProcessImportCSVChangeHistory
                                FOREACH ($obj in $ImportedFile)
                                {
                                    $Output = "" | Select-Object -Property DateTime, State, DisplayName,Name, SamAccountName, DN
                                    #$Output.DateTime = $file.CreationTime.GetDateTimeFormats("u") | Out-String
                                    $Output.DateTime = $obj.DateTime
                                    $Output.State = $obj.State
                                    $Output.DisplayName = $obj.DisplayName
                                    $Output.Name = $obj.Name
                                    $Output.SamAccountName = $obj.SamAccountName
                                    $Output.DN = $obj.DN
                                    $infoChangeHistory = $infoChangeHistory + $Output
                                }#FOREACH $obj in Import-csv $file
                            }#FOREACH $file in $ChangeHistoryFiles
                            Write-Verbose -Message "[PROCESS] $item - Change history process completed"
                        }#IF($ChangeHistoryFiles)
                        
                        # CHANGE(S) EXPORT TO CSV
                        Write-Verbose -Message "[PROCESS] $item - Save changes to a ChangesHistory file"
                        
                        IF (-not (Test-Path -path (Join-Path -Path $ScriptPathChangeHistory -ChildPath "$($DomainName)_$($RealGroupName)-ChangeHistory.csv")))
                        {
                            $Changes | Export-Csv -Path (Join-Path -Path $ScriptPathChangeHistory -ChildPath "$($DomainName)_$($RealGroupName)-ChangeHistory.csv") -NoTypeInformation
                        }
                        ELSE
                        {
                            #$Changes | Export-Csv -Path (Join-Path -Path $ScriptPathChangeHistory -ChildPath "$DomainName_$RealGroupName-ChangeHistory-$DateFormat.csv") -NoTypeInformation
                            $Changes | Export-Csv -Path (Join-Path -Path $ScriptPathChangeHistory -ChildPath "$($DomainName)_$($RealGroupName)-ChangeHistory.csv") -NoTypeInformation -Append
                        }
                        
                        
                        # EMAIL
                        Write-Verbose -Message "[PROCESS] $item - Preparing the notification email..."
                        
                        $EmailSubject = "PS MONITORING - $($GroupName.SamAccountName) Membership Change"
                        
                        # Preparing the body of the Email
                        $body = "<h2>Group: $($GroupName.SamAccountName)</h2>"
                        $body += "<p style=`"background-color:white;font-family:consolas;font-size:8pt`">"
                        $body += "<u>Group Description:</u> $($GroupName.Description)<br>"
                        $body += "<u>Group DistinguishedName:</u> $($GroupName.DistinguishedName)<br>"
                        $body += "<u>Group CanonicalName:</u> $($GroupName.CanonicalName)<br>"
                        $body += "<u>Group SID:</u> $($GroupName.Sid.value)<br>"
                        $body += "<u>Group Scope/Type:</u> $($GroupName.GroupScope) / $($GroupName.GroupType)<br>"
                        $body += "</p>"
                        
                        $body += "<h3> Membership Change"
                        $body += "</h3>"
                        $body += "<i>The membership of this group changed. See the following Added or Removed members.</i>"
                        
                        # Removing the old DisplayName Property
                        $Changes = $changes | Select-Object -Property DateTime, State,Name, SamAccountName, DN
                        
                        $body += $changes | ConvertTo-Html -head $head | Out-String
                        $body += "<br><br><br>"
                        IF ($ChangesHistoryFiles)
                        {
                            # Removing the old DisplayName Property
                            $infoChangeHistory = $infoChangeHistory | Select-Object -Property DateTime, State, Name, SamAccountName, DN
                            
                            $body += "<h3>Change History</h3>"
                            $body += "<i>List of the previous changes on this group observed by the script</i>"
                            $body += $infoChangeHistory | Sort-Object -Property DateTime -Descending | ConvertTo-Html -Fragment -PreContent $Head2 | Out-String
                        }
                        $body = $body -replace "Added", "<font color=`"blue`"><b>Added</b></font>"
                        $body = $body -replace "Removed", "<font color=`"red`"><b>Removed</b></font>"
                        $body += $Report
                        
                        # Preparing the Email properties
                        $SmtpClient = New-Object -TypeName system.net.mail.smtpClient
                        $SmtpClient.host = $EmailServer
                        $MailMessage = New-Object -TypeName system.net.mail.mailmessage
                        #$MailMessage.from = $EmailFrom.Address
                        $MailMessage.from = $EmailFrom
                        #FOREACH ($To in $Emailto){$MailMessage.To.add($($To.Address))}
                        FOREACH ($To in $Emailto) { $MailMessage.To.add($($To)) }
                        $MailMessage.IsBodyHtml = 1
                        $MailMessage.Subject = $EmailSubject
                        $MailMessage.Body = $Body
                        
                        # Encoding
                        $MailMessage.BodyEncoding = [System.Text.Encoding]::$EmailEncoding
                        $MailMessage.SubjectEncoding = [System.Text.Encoding]::$EmailEncoding

                        
                        # Sending the Email
                        $SmtpClient.Send($MailMessage)
                        Write-Verbose -Message "[PROCESS] $item - Email Sent."
                        
                        
                        # GroupName Membership export to CSV
                        Write-Verbose -Message "[PROCESS] $item - Exporting the current membership to $StateFile"
                        $Members | Export-csv -Path (Join-Path -Path $ScriptPathOutput -ChildPath $StateFile) -NoTypeInformation -Encoding Unicode
                        
                        # Export HTML File
                        IF ($PSBoundParameters['HTMLLog'])
                        {
                            # Create HTML Directory if it does not exist
                            $ScriptPathHTML = $ScriptPath + "\HTML"
                            IF (!(Test-Path -Path $ScriptPathHTML))
                            {
                                Write-Verbose -Message "[PROCESS] Creating the HTML Folder : $ScriptPathHTML"
                                New-Item -Path $ScriptPathHTML -ItemType Directory | Out-Null
                            }
                            
                            # Define HTML File Name
                            $HTMLFileName = "$($DomainName)_$($RealGroupName)-$(Get-Date -Format 'yyyyMMdd_HHmmss').html"
                            
                            # Save HTML File
                            $Body | Out-File -FilePath (Join-Path -Path $ScriptPathHTML -ChildPath $HTMLFileName)
                        }
                        
                        
                    }#IF $Change
                    ELSE { Write-Verbose -Message "[PROCESS] $item - No Change" }
                    
                }#IF ($GroupName)
                ELSE
                {
                    Write-Verbose -message "[PROCESS] $item - Group can't be found"
                    #IF (Get-ChildItem (Join-Path $ScriptPathOutput "*$item*-membership.csv" -ErrorAction Continue) -or (Get-ChildItem (Join-Path $ScriptPathChangeHistory "*$item*.csv" -ErrorAction Continue)))
                    #{
                    # Write-Warning "$item - Looks like a file contains the name of this group, this group was possibly deleted from Active Directory"
                    #}
                    
                }#ELSE $GroupName
            }#TRY
            CATCH
            {
                Write-Warning -Message "[PROCESS] Something went wrong"
                #Write-Warning -Message $_.Exception.Message
                Write-Warning -Message $Error[0]
                
                #Quest Snappin Errors
                if ($ErrorProcessGetQADGroup) { Write-warning -Message "[PROCESS] QUEST AD - Error When querying the group $item in Active Directory" }
                if ($ErrorProcessGetQADGroupMember) { Write-warning -Message "[PROCESS] QUEST AD - Error When querying the group $item members in Active Directory" }
                
                #ActiveDirectory Module Errors
                if ($ErrorProcessGetADGroup) { Write-warning -Message "[PROCESS] AD MODULE - Error When querying the group $item in Active Directory" }
                if ($ErrorProcessGetADGroupMember) { Write-warning -Message "[PROCESS] AD MODULE - Error When querying the group $item members in Active Directory" }
                
                # Import CSV Errors
                if ($ErrorProcessImportCSV) { Write-warning -Message "[PROCESS] Error Importing $StateFile" }
                if ($ErrorProcessCompareObject) { Write-warning -Message "[PROCESS] Error when comparing" }
                if ($ErrorProcessImportCSVChangeHistory) { Write-warning -Message "[PROCESS] Error Importing $file" }
                
                Write-Warning -Message $error[0].exception.Message
            }#CATCH
        }#FOREACH
    }#TRY
    CATCH
    {
        Write-Warning -Message "[PROCESS] Something wrong happened"
        #Write-Warning -Message $error[0].exception.message
        Write-Warning -Message $error[0]
    }
    
}#PROCESS
END
{
    Write-Verbose -message "[END] Script Completed"
}