locked
Need help to improve powershell audit ad script RRS feed

  • Question

  • Hi,

    I make this script to audit active directory infrastructure and I would like you give me feedback to know what I have to improve pls.

    # =======================================================
    # NAME: auditAD.ps1
    # AUTHOR: 
    # DATE: 15/01/2020
    #
    # 
    # 
    # This script is used to created HTML report for Active directory Audit
    #
    #Requires -Version 3.0
    #Requires -Modules ActiveDirectory
    # =======================================================
    
    
    ##Configuration partition d'annuaire ##
    
    #Initialisation des variables globales 
    #Récupération du nom de la forêt, des noms des domaines et de tous les DCs.
    #Emplacement du fichier html
    #Global variable
    #forest is current forest
    #forest name is curent forest name
    #domaine name is all domains name in the current forest
    #allDCs is all DC in the entire forest
    #htmlReportPath is the path where the HTML report is created
    $forest=get-adforest
    $forestName=$forest.Name
    $domainName=$forest.domains
    $allDCs = (Get-ADForest).Domains | Foreach-Object { Get-ADDomainController -Filter * -Server $_ }
    $htmlReportPath = "c:\ADReport.html"
    
    #Check if ActiveDirectory Recyclebin is enable or not
    #If it's disabled result is null 
    #If it's enabled result is an object collection
    function Get-RecycleBinState {
        if ((Get-ADOptionalFeature -Filter 'name -eq "Recycle Bin Feature"').EnabledScopes)
        {
            $recyclebinState= $true
        }
        else
        {
            $recyclebinState= $false
        }
        $result=[PSCustomObject]@{
            RecycleBin = "RecycleBin"
            State = $RecyclebinState
        }
        return $result    
    }
    
    
    #Schema version
    function Get-SchemaVersion {
        $SchemaVersion = (Get-ADObject (Get-ADRootDSE).schemaNamingContext -Property objectVersion).objectVersion 
        switch ($SchemaVersion)
        {
            '13' { $result = "13 - Windows 2000"}
            '30' { $result = "30 - Windows 2003"}
            '31' { $result = "31 - Windows 2003 R2"}
            '44' { $result = "44 - Windows 2008"}
            '47' { $result = "47 - Windows 2008 R2"}
            '56' { $result = "56 - Windows 2012"}
            '69' { $result = "69 - Windows 2012 R2"}
            '87' { $result = "87 - Windows 2016"}
            '88' { $result = "88 - Windows 2019"}
            Default { $result = "La valeur n'est pas renseignée dans le script"}
        }
        return $result
    }
    
    #Get summary informations about AD forest
    #Forest functional level, schema master, schema version, domain naming master, tombstone lifetime, recyclebin state and all domains names
    function Get-ForestSummary {
        $result=[PSCustomObject][ordered]@{
            ForestName = $forestname
            'Forest Functional Level' = $forest.ForestMode
            'Schema Master' =  $forest.schemamaster
            'Schema Version' = $SchemaVersion
            'Domain Naming Master' = $forest.domainnamingmaster
            'Tombstone lifetime' = (Get-ADObject -Identity “CN=Directory Service,CN=Windows NT,CN=Services,$((Get-ADRootDSE).configurationNamingContext)” -Properties tombstoneLifetime).tombstoneLifetime
            RecycleBin = $recyclebin.state
            DomainName = $domainname -join ", "
        }
        return $result
    }
    
    #Get all FSMO domains rôles in all domains (infrastructure master, RID master and PDC emulator) and the domain functional level
    function Get-FSMOAllDomains {
        $result=$domainName | Foreach-Object {
            $domain = Get-ADDomain $_
    
                [PSCustomObject]@{
                Domain = $_
                InfrastructureMaster = $domain.infrastructuremaster.split(".")[0]
                RIDMaster = $domain.RIDMaster.split(".")[0]
                PDCEmulator = $domain.PDCEmulator.split(".")[0]
                DomainFunctionalLevel=$domain.domainmode
            }
        }
        return $result
    }
    
    #Get number of duplicates SPNs in the entire forest and their account
    Function Get-DuplicateSPNNumber
    {
        $command = setspn -X -F
        $query = $command -match "(?mi)^(\d+)"
        if ($query -match "^0")
        {
            return $query
        }
        else
        {
            return $command
        }
    } 
    
    #Check if DCs are Global Catalogs 
    Function Get-GlobalCatalogServers
    {
        foreach ($DC in $allDCs)
        {
            [PSCustomObject]@{
                ServerName = $DC.hostname
                IsGlobalCatalog = $DC.IsGlobalCatalog
            }
    
        }
    } 
    
    #Check if all DC are Read only domain controller
    Function Get-RODCServers
    {
        foreach ($DC in $allDCs)
        {
            [PSCustomObject]@{
                ServerName = $DC.hostname
                IsRODCServer = $DC.IsReadOnly
            }
    
        }
    } 
    
    #Give time source from all DCs
    function Get-TimeSource {
        $result=$alldcs | Foreach-Object {
            $req=Invoke-Command -ComputerName $_.hostname -ScriptBlock { w32tm /query /source }
            [PSCustomObject]@{
                Server = $_.name
                TimeSource = $req
            }
        }
        return $result
    }
    
    #List all AD sites and associated subnets
    $SitesSubnets=[DirectoryServices.ActiveDirectory.Forest]::GetCurrentForest().Sites.subnets
    
    #List all sites costs and replication schedule 
    function Get-SiteLinkInformation {
        $SiteLinkParam = @{
            Filter = 'objectClass -eq "siteLink"'
            Searchbase = (Get-ADRootDSE).ConfigurationNamingContext
            Property = @("Options", "Cost", "ReplInterval", "SiteList", "Schedule")
        }
        $param = @(
            'Name',
            @{Name="SiteCount";Expression={$_.SiteList.Count}},
            'Cost', 
            'ReplInterval',
            @{Name="Schedule";Expression={If($_.Schedule){If(($_.Schedule -Join " ").Contains("240")){"NonDefault"}Else{"24x7"}}Else{"24x7"}}}, 
            'Options'
        )
        $result=Get-ADObject @SiteLinkParam | 
        Select-Object $param
        
        return $result
    }
    
    
    #list sites with domain controler,subnet and site link number
    function Get-EmptySite {
        $BadSitesParam = @{
            LDAPFilter = '(objectClass=site)'
            SearchBase = (Get-ADRootDSE).ConfigurationNamingContext
            Properties = @("WhenCreated", "Description")
        }
        $param=@(
            'name', 
            @{label='IsEmpty';expression={If ($(Get-ADObject -Filter {ObjectClass -eq "nTDSDSA"} -SearchBase $_.DistinguishedName)) {$false} else {$true}}}, 
            @{label='DCName';expression={@($((Get-ADObject -Filter {ObjectClass -eq "nTDSDSA"} -SearchBase $_.DistinguishedName) | ForEach-Object { $_.distinguishedname.split(",")[1].split("=")[1]}))-join ", "}},
            @{label='DCCount';expression={@($(Get-ADObject -Filter {ObjectClass -eq "nTDSDSA"} -SearchBase $_.DistinguishedName)).Count}},
            @{label='SubnetCount';expression={@($(Get-ADObject -Filter {ObjectClass -eq "subnet" -and siteObject -eq $_.DistinguishedName} -SearchBase (Get-ADRootDSE).ConfigurationNamingContext)).Count}},
            @{label='SiteLinkCount';expression={@($(Get-ADObject -Filter {ObjectClass -eq "sitelink" -and siteList -eq $_.DistinguishedName} -SearchBase (Get-ADRootDSE).ConfigurationNamingContext)).Count}}
            
        )
        $result=Get-ADObject @BadSitesParam |
        Select-Object $param
    
        return $result
    }
    
    #Check all necessary services for AD (ntfrs or dfsr,netlogon,kdc,w32time)
    Function Get-ADServicesStatus
    {
        $services =@("ntfrs","dfsr","netlogon","kdc","w32time")
        foreach ($DC in $allDCs)
        {
            foreach ($service in $services)
            {
                $query = get-service $service -ComputerName $DC
                [PSCustomObject]@{
                    ComputerName = $DC
                    ServiceName = $query.name
                    ServiceStatus = $query.status
                }
            }
        }
    } 
    
    
    #Execute DCdiag on all Domain Controllers
    #this function is implemented to be able to capture dcdiag output and convert it to an object with regex
    #Nativement dcdiag a pour sortie du texte. Cette fonction permet de faire un objet en sortie.
    function Invoke-DcDiag {
        param(
            [Parameter(Mandatory)]
            [ValidateNotNullOrEmpty()]
            [string]$DomainController
        )
        $command = dcdiag.exe /s:$DomainController
        $result=@()
        #Cette ligne est nécessaire pour que l'éxécution du script fonctionne dans l'ISE en francais
        [Console]::OutputEncoding = [System.Text.Encoding]::GetEncoding(437)    
       
       # connaitre la langue courante du système : (Get-Culture).lcid)  1033 = anglais 1036 = francais
    
       #regex for french language
        $regex = [regex]"(?sm)\.+\sLe\stest\s(\w+).+?de\s([A-Za-z0-9_-]+)\sa\s(réussi|échoué)" 
        $allmatches = $regex.Matches($command)
        Foreach($line in $allmatches){
            $ObjectDiag = [PSCustomObject]@{
                Server = $DomainController.Split(".")[0]
                TestName = $line.Groups[1].Value
                TestResult = $line.Groups[3].Value
            }
            $result+=$ObjectDiag
        }
        return $result
    }
    
    #Last Time Microsoft update is installed on all DCs
    Function Get-LastUpdateDate
    {
        $hotfixes=((get-hotfix).properties | Where-Object {$_.name -eq "installedon"}).value 
        $temp = @()
        foreach ($hotfix in $hotfixes)
        {
            $temp+=[datetime]$hotfix
        }
        $result = $temp | Sort-Object -Descending | Select-Object -first 1
        return $result.DateTime
    } 
    
    #Get Bitlocker State on all DCs
    #If bitlocker module is installed, list all volume and check if they are encrypted.
    #If bitlocker module is not installed, volume are not encrypted.
    Function Get-BitlockerState
    {
        foreach ($DC in $allDCs)
        {
            #Check if bitlocker module is installed on the DC
            if ($null -ne (Invoke-Command -ComputerName $DC.hostname -ScriptBlock {get-command -Module bitlocker}))
            {
                $query=Invoke-Command -ComputerName $DC.hostname -ScriptBlock {get-bitlockervolume | sort-object MountPoint}
    
                foreach ($line in $query)
                {
                    [PSCustomObject]@{
                        ServerName = $line.computerName
                        IsInstalled = $True           
                        MountPoint = $line.MountPoint
                        VolumeStatus = $line.volumeStatus
                    }
                }
            }        
            else
            {
            
                [PSCustomObject]@{
                    ServerName = $DC.hostname   
                    IsInstalled = $false        
                    MountPoint = ""
                    VolumeStatus = ""
                }
            }
            
        }
    }
    
    
    #List all sensitive group member in all domains
    $SchemaAdmin = (Get-ADGroupMember -Identity "$((get-addomain).domainsid.value)-518" -Recursive).name 
    $EnterpriseAdminsMember=(Get-ADGroupMember -Identity "$((get-addomain).domainsid.value)-519" -Recursive).name
    $DomainsAdminsMember=$domainName | Foreach-Object {write-output "Domaine : $_ ";(Get-ADGroupMember -Identity "$((get-addomain).domainsid.value)-512" -Recursive).name}
    $AccountOperator = $domainName | Foreach-Object {write-output "Domaine : $_ ";(Get-ADGroupMember -Identity S-1-5-32-548 -Recursive).name}
    $Administrators = $domainName | Foreach-Object {write-output "Domaine : $_ ";(Get-ADGroupMember -Identity S-1-5-32-544 -Recursive).name}
    $BackupOperators = $domainName | Foreach-Object {write-output "Domaine : $_ ";(Get-ADGroupMember -Identity S-1-5-32-551 -Recursive).name}
    $ServerOperators = $domainName | Foreach-Object {write-output "Domaine : $_ ";(Get-ADGroupMember -Identity S-1-5-32-549 -Recursive).name}
    
    #Get all accounts with AdminCount = 1
    Function Get-PrivilegedAccounts
    {
        foreach ($domain in $domainName){        
            $Findings = Get-ADObject -Filter {(AdminCount -eq 1)} -Server $domain | Get-ADGroupMember -Recursive -ErrorAction SilentlyContinue | Select-Object -Unique *
            $Findings | ForEach-Object {
                [PSCustomObject]@{
                    DomainName = $domain
                    PrivilegedAccount = $_.name
                }
            }
        }   
    } 
    
    #Get status of firewall for all profiles (domain, private, public) in all DC
    Function Get-FirewallState
    {
        foreach ($DC in $allDCs)
        {
            $query = Invoke-Command -ComputerName $DC -ScriptBlock {Get-NetFirewallProfile} 
            [PSCustomObject]@{
                ServerName = $DC.hostname
                Domain = ($query | Where-Object {$_.profile -eq "Domain"}).enabled         
                Private = ($query | Where-Object {$_.profile -eq "Private"}).enabled 
                Public = ($query | Where-Object {$_.profile -eq "Public"}).enabled 
            }        
        }
    } 
    
    #Hardware Configuration of all DCs (Logical CPU number,RAM, physical or virtual, OS, last boot)
    function Get-AllDCHardware
    {
        $result=$allDCs | Foreach-Object {
            [PSCustomObject]@{
                ServerName = $_.name
                LogicalCPUNb = (Get-CimInstance win32_computersystem -ComputerName $_.hostname).numberOfLogicalProcessors
                'RAM(GB)' = [math]::Round((Get-CimInstance win32_computersystem -ComputerName $_.hostname).TotalPhysicalMemory/1GB)
                Type = (get-CimInstance win32_computersystem -ComputerName $_.hostname).model
                OS = (Get-CimInstance Win32_OperatingSystem -ComputerName $_.hostname).caption
                LastBoot = (Get-CimInstance -ClassName win32_operatingsystem -ComputerName $_.hostname).lastbootuptime
            }
        }
        return $result
    }
    
    #Disk space usage of all DCs
    function Get-AllDCDiskSettings {
        $result = $allDCs | Foreach-Object {
            $disk=get-CimInstance -class win32_logicaldisk -ComputerName $_.name | Where-Object {$_.DriveType -eq 3}
            $disk | Foreach-Object {
                [PSCustomObject]@{
                    ServerName = $_.systemname
                    DiskLetter = $_.DeviceID
                    'FreeSpace(GB)' = [math]::Round($_.FreeSpace/1GB)
                    'TotalSpace(GB)' = [math]::Round($_.size/1GB)
                }
            }
        }
        return $result
    }
    
    #Network setup of all DCs (IP,subnet,gateway,DNS1,DNS2)
    function Get-AllDCNetworkSettings {
        $result = $alldcs | Foreach-Object {
            $req=Get-CimInstance -Class "win32_networkadapterconfiguration" -ComputerName $_.name | Where-Object {$_.ipenabled -eq "true"}
            [PSCustomObject]@{
                Name = $_.name
                IPAddress = $req.ipaddress[0]
                Subnet = $req.ipsubnet[0]
                Gateway = $req.defaultipgateway[0]
                DNS1 = $req.dnsserversearchorder[0]
                DNS2 = $req.dnsserversearchorder[1]
            }
        }
        return $result
    }
    
    #List default OU for computer and OU creation in domain
    Function Get-OURedirect
    {
        $domainName | Foreach-Object {
            [PSCustomObject]@{
                DomainName = $_
                DefaultComputersOU = (Get-ADDomain -Identity $_).ComputersContainer     
                DefaultUsersOU = (Get-ADDomain -Identity $_).UsersContainer
            }
        }   
    } 
    
    #List Active directory base and logs paths and size
    function Get-AllDCNTDSSettings {
        $result=$allDCs | Foreach-Object {
            $ntdsConfig=invoke-command -computername $_.name -scriptblock {get-itemproperty "HKLM:\SYSTEM\CurrentControlSet\Services\ntds\Parameters"}
            [PSCustomObject]@{
                Name = $_.name
                DBPath = $ntdsConfig.'DSA Database file'
                DBLogPath = $ntdsConfig.'Database log files path'
                'DBSize(MB)' = [math]::Round((get-childitem $($ntdsConfig.'DSA Database file')).length/1MB)
            }
        }
        return $result
    }
    
    #Check if Netlogon and Sysvol folders are shared
    Function Get-ADSharedFolderStatus
    {
        $folders =@("netlogon","sysvol")
        foreach ($DC in $allDCs)
        {
            foreach ($folder in $folders)
            {
                $query = get-smbshare -CimSession $DC.hostname -Name $folder
                [PSCustomObject]@{
                    ServerName = $DC.hostname
                    SharedFolderName = $folder
                    Present = $query.ShareState
                }
            }
        }
    }
    
    #Check if GPO (SYSVOL folder) is replicated by FRS or DFS-R mechanism
    #If msDFSR-Flags attribute equal empty FRS is used 
    #If msDFSR-Flags attribute equal 0  migration from FRS to DFSR is started 
    #If msDFSR-Flags attribute equal 16 migration from FRS to DFSR is prepared
    #If msDFSR-Flags attribute equal 32 migration from FRS to DFSR is redirected 
    #If msDFSR-Flags attribute equal 48 migration from FRS to DFSR is eliminated (ended) 
    
    function Get-SysvolReplicationType {
        $result = $domainName| Foreach-Object {
            $DFSRFlags=(Get-ADObject -identity "CN=DFSR-GlobalSettings,$((Get-ADDomain).systemscontainer)" -properties msDFSR-Flags).'msDFSR-Flags'
            $query=$DFSRFlags -eq 48
            [PSCustomObject]@{
                Domain = $_ 
                DFSR = $query
                FRS = -not $query
            }
        }
        return $result
    }
    
    #SYSVOL folder and size in KB
    function Get-SysvolSummary {
        $result=$domainName| Foreach-Object {
        $syssize=(get-childitem "\\$_\sysvol" -recurse | Measure-Object -property Length -Sum).sum / 1KB
        $getADDomain=Get-ADDomain
            [PSCustomObject]@{
                Domain = $_
                'SYSVOLSize(KB)' = [math]::Round($syssize)
                RootFolderSYSVOL = (get-childitem "\\$($getADDomain.forest)\sysvol\$($getADDomain.forest)").name -join ", "
                PolicyFolderSYSVOL = (get-childitem "\\$($getADDomain.forest)\sysvol\$($getADDomain.forest)\policies").name -join ", "
                ScriptFolderSYSVOL = (get-childitem "\\$($getADDomain.forest)\sysvol\$($getADDomain.forest)\scripts").name -join ", "
                TotalCreatedGPONb = (get-gpo -All).count
            }
        }
        return $result
    }
    
    
    
    
    
    #Total Computer and users number and total disabled  
    Function Get-ADAccountState {
        [PSCustomObject]@{
            TotalUsers = (Get-ADUser -filter *).count
            DisabledUsers = (Get-AdUser -filter * |Where-Object {$_.enabled -eq $False}).count
            TotalComputers = (Get-ADComputer -filter *).count
            DisabledComputers = (Get-AdUser -filter * |Where-Object {$_.enabled -eq $False }).count
        }        
    } 
    
    #Computers and users disabled and where password never expire
    Function Get-ADPasswordState{
        [PSCustomObject]@{
            UsersPasswordNeverExpire = (get-aduser -filter * -properties passwordneverexpires | Where-Object {$_.passwordneverexpires -eq $true}).count
            ComputersPasswordNeverExpire = (get-adcomputer -filter * -properties passwordneverexpires | Where-Object {$_.passwordneverexpires -eq $true}).count
            UsersLocked = (get-aduser -filter * -properties LockedOut | Where-Object {$_.LockedOut -eq $true}).count
            ComputersLocked = (get-adcomputer -filter * -properties LockedOut | Where-Object {$_.LockedOut -eq $true}).count
        }        
    } 
    
    
    <#
    #paramétrage IP (a lancer sur chaque DC) NON COMPATIBLE 2008R2
    $allDCs = (Get-ADForest).Domains | %{ Get-ADDomainController -Filter * -Server $_ }
    $NetworkConfigDC=$allDCs | Foreach-Object {
        $req=Invoke-Command -ComputerName $_.name -ScriptBlock { Get-NetIPConfiguration}
    
        [PSCustomObject]@{
            Name = $_.name
            InterfaceAlias = $req.InterfaceAlias
            Ipaddress = $req.IPv4Address.ipaddress
            Mask = $req.IPv4Address.prefixLength
            Gateway = $req.IPv4DefaultGateway.nexthop
            DNS = $req.DNSServer.serveraddresses | where { $_ -ne "::1"}
        }
    }
    #>
    
    
    
    
    
    
    ###### Exécution des commandes
    
    $summary = Get-ForestSummary
    $SchemaVersion = Get-SchemaVersion
    $recyclebin = Get-RecycleBinState 
    $FSMODomain = Get-FSMOAllDomains
    $DuplicateSPNNumber = Get-DuplicateSPNNumber
    $GlobalCatalogServers = Get-GlobalCatalogServers
    $RODCServers = Get-RODCServers
    $TimeSource = Get-TimeSource
    $SiteLink = Get-SiteLinkInformation 
    $BadSites = Get-EmptySite
    $ADServicesStatus = Get-ADServicesStatus
    $ADServicesStatusResult = $ADServicesStatus | Where-Object {$_.ServiceStatus -eq "Stopped"} 
    $dcdiag = $alldcs | Foreach-Object {Invoke-DcDiag -DomainController $_.hostname}
    $dcdiagResult = $dcdiag| Group-Object testresult | Select-Object name,count
    $dcdiagError = ($dcdiag | Group-Object testresult | where-object {$_.name -eq "échoué"}).group
    $LastUpdateTime = Get-LastUpdateDate
    $BitlockerState = Get-BitlockerState
    
    $FirewallState = Get-FirewallState
    $DCHardware = Get-AllDCHardware
    $DCDiskHardware = Get-AllDCDiskSettings
    $DCNetworkConfig = Get-AllDCNetworkSettings 
    $DCNTDSConfig = Get-AllDCNTDSSettings 
    $ADSharedFolderStatus = Get-ADSharedFolderStatus
    $SYSVOLReplication = Get-SysvolReplicationType 
    $SYSVOLSize = Get-SysvolSummary
    $OURedirect = Get-OURedirect
    $PrivilegedAccounts = Get-PrivilegedAccounts
    $ADAccountState = Get-ADAccountState
    $ADPasswordState = Get-ADPasswordState
    
    
    #region HTML
    @"
    <!DOCTYPE html>
    <html>
    <head>
    <style>
    table {
       # border-collapse: collapse;
       border-width: 1px; border-style: solid; border-color: black; border-collapse: collapse;
    }
    h2 {text-align:center}
    th, td {
        #padding: 8px;
        #text-align: left;
        #border-bottom: 1px solid #ddd;
        border-width: 1px; padding: 3px; border-style: solid; border-color: black;
    }
    
    tr:hover{background-color:#f5f5f5}
    </style>
    </head>
    <body>
    <h1>Active Directory Report for forest $forestname</h1>
    Date : $(get-date -Format "dd/MM/yyyy HH:mm")
     
    
    <h3>Generalites</h3>
    $($Summary | ConvertTo-Html -as list -Fragment) 
    
    <h3> FSMO Domain Roles </h3>
    $($FSMODomain | ConvertTo-Html -Fragment) 
    
    <h3> Duplicates SPN </h3>
    $($DuplicateSPNNumber | ConvertTo-Html -property @{label='DuplicateSPNNumber' ; expression = {$_}} -Fragment) 
    
    <h3> Global Catalogs </h3>
    $($GlobalCatalogServers | ConvertTo-Html -Fragment) <br>
    
    <h3> RODC </h3>
    $($RODCServers | ConvertTo-Html -Fragment)
    
    <h3>Source de temps</h3>
    $($TimeSource | ConvertTo-Html -Fragment)
    
    
    <h3>Sites et Services AD</h3>
    <h4> Reseaux </h4>
    $($SitesSubnets | ConvertTo-Html -Fragment) 
    <h4> Liens de site </h4>
    $($SiteLink | ConvertTo-Html -Fragment) 
    <h4> Resume et sites vide </h4>
    $($BadSites | ConvertTo-Html -Fragment)
    
    <h3>Etats Services AD</h3>
    $($ADServicesStatus | ConvertTo-Html -Fragment) <br>
    Services Arrêtés
    $($ADServicesStatusResult | ConvertTo-Html -Fragment)
    
    <h3>Dcdiag</h3>
    $($dcdiag | ConvertTo-Html -Fragment) <br>
    Résumé des résultats
    $($dcdiagResult | ConvertTo-Html -Fragment) <br>
    Résultats en erreurs 
    $($dcdiagError | ConvertTo-Html -Fragment) 
    
    <h3>Dernière date de mise à jour</h3>
    $($LastUpdateTime | ConvertTo-Html -property @{label='Last Update Date' ; expression = {$_}} -Fragment)
    
    <h3>Chiffrement avec Bitlocker</h3>
    $($BitlockerState | ConvertTo-Html -Fragment)
    
    <h3>Structure des OU</h3>
    $($OUHierarchy | ConvertTo-Html -property @{label='OU Name' ; expression = {$_.CanonicalName}} -Fragment)
    
    <h3>Membres du groupe Administrateur du schema</h3>
    $($SchemaAdmin | ConvertTo-Html -property @{label='Schema Admin' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Administrateurs de l entreprise</h3>
    $($EnterpriseAdminsMember | ConvertTo-Html -property @{label='Enterprise Admins' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Admins du domaine</h3>
    $($DomainsAdminsMember | ConvertTo-Html -property @{label='Domain Admins' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Operateurs de compte</h3>
    $($AccountOperator | ConvertTo-Html -property @{label='Account operators' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Administrateurs</h3>
    $($Administrators | ConvertTo-Html -property @{label='Administrateurs' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Operateurs de sauvegarde</h3>
    $($BackupOperators | ConvertTo-Html -property @{label='Backup operators' ; expression = {$_}} -Fragment)
    
    <h3>Membres du groupe Operateurs de serveur</h3>
    $($ServerOperators | ConvertTo-Html -property @{label='Server operators' ; expression = {$_}} -Fragment)
    
    <h3>Membres des groupes privilegies</h3>
    $($PrivilegedAccounts | ConvertTo-Html -Fragment)
    
    <h3>Etat du parefeu</h3>
    $($FirewallState | ConvertTo-Html -Fragment)
    
    <h3>Configuration matérielle des DCs</h3>
    $($DCHardware | ConvertTo-Html -Fragment)<br>
    $($DCDiskHardware | ConvertTo-Html -Fragment)
    
    <h3>Configuration réseau des DCs</h3>
    $($DCNetworkConfig | ConvertTo-Html -Fragment)
    
    <h3>OU par defaut lors de la creation de compte</h3>
    $($OURedirect | ConvertTo-Html -Fragment)
    
    <h3>Configuration de la base NTDS des DC</h3>
    $($DCNTDSConfig | ConvertTo-Html -Fragment)
    
    <h3>Etats des partages netlogon et sysvol</h3>
    $($ADSharedFolderStatus | ConvertTo-Html -Fragment)
    
    <h3>réplication du SYSVOL</h3>
    $($SYSVOLReplication | ConvertTo-Html -Fragment)
    
    <h3>Taille du SYSVOL</h3>
    $($SYSVOLSize | ConvertTo-Html -Fragment)
    
    <h3>Serveurs DHCP autorises</h3>
    $($AllowedDHCPServers | ConvertTo-Html -Fragment)
    
    <h3>Total des comptes et comptes desactives</h3>
    $($ADAccountState | ConvertTo-Html -Fragment)
    
    <h3>Comptes desactives et verouilles</h3>
    $($ADPasswordState | ConvertTo-Html -Fragment)
    
    </body>
    "@ | Out-File -Encoding utf8 $htmlReportPath
    #endregion
    
    Invoke-Item $htmlReportPath
    




    Merci de marquer comme reponses les interventions qui vous ont ete utile.



    • Edited by matteu31400 Friday, January 17, 2020 1:49 PM
    Wednesday, January 15, 2020 10:59 AM

All replies

  • Of course you are kidding. THis is not a classroom and it is unlikely anyone here will analyze your whole script.

    I recommend running it through the PowerShell Script Analyzer first as it will find all of the most obvious deficiencies.

    https://devblogs.microsoft.com/powershell/release-of-powershell-script-analyzer-psscriptanalyzer-1-18-2/


    \_(ツ)_/

    Wednesday, January 15, 2020 5:49 PM
  • Hi,

    I'm not kidding.

    I just ask interested people to test it and give me feedback about what could be missing.

    I don't ask really about how to improve my powershell code cause I know it's really not easy to find people for this when script are more than some line...

    Thanks for this tool but I already know it.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Wednesday, January 15, 2020 6:25 PM
  • What improvements are you seeking? You failed to illuminate us on that detail.

    You might also review the following and also fix you code so it is displayed correctly. It is all one color when viewed.


    \_(ツ)_/

    Wednesday, January 15, 2020 6:29 PM
  • If I missed some important informations in my script.

    I think it should be tested by Active directory people and I would like their feedback to know what could be better to add in the script and what is not really relevant.

    I see it's because of the backtick my script is not on the same color, I will see your link to correct my code.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Wednesday, January 15, 2020 7:11 PM
  • First rule of style with PowerShell - don't use line extenders (back-tic).  It is never needed.

    This would be the best way to lay out a long statement so it is readable and understandable and avoids line extenders.  The props can be moved out of the way to a script or global scope and will still work inside of a loop or other construct as the code is parsed in the current context and scope.

    $props = @(
        'Name',
        @{n='SiteCount';e={$_.SiteList.Count}},
        'Cost', 
        'ReplInterval',
        @{n='Schedule';e={
                    if($_.Schedule){
                        if(($_.Schedule -contains 240){
                            'NonDefault'
                        }else{
                            '24x7'}
                    }else{
                        '24x7'}
                    }}, 
        'Options'
    )
    Select-Object $props

    The back-tics will always screw up posting and make code hard to understand and debug.  The back-tic is an option useful for typing at a prompt but is seldom ever really needed when you learn how PS parses commands.


    \_(ツ)_/


    • Edited by jrv Wednesday, January 15, 2020 7:38 PM
    Wednesday, January 15, 2020 7:36 PM
  • I also recommend against writing your own AD audit tool for many reasons but first is that MS already has an audit tool that is comprehensive and years ahead of anything you can script.  It is installed by default on all versions of AD from WS2012 on and can be downloaded and run on any system.

    Here are the docs: AD DS Best Practices Analyzer

    It does many times more than what you are trying to do and is more reliable as it allows us to compare audits periodically and generate very nice reports that can be viewed on any web server or stand alone.  It also comes with PowerShell CmdLets that allow you to script and customize scans.


    \_(ツ)_/

    Wednesday, January 15, 2020 7:51 PM
  • Script uploaded with here string to correct display :)

    Now, display is not good on the end of my script but it's maybe because it's html ?


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 1:43 PM
  • BPA are great and interesting yes but it's just unsufficient...

    Customer wand complete audit of their AD environment and BPA is just here to say :

    This is not configured like best practice.This script give lot of informations about the entire environment. I improve my powershell skill with it and write something usefull for my job and I would like people feedback to add what could be usefull :)

    I know result is not beautifull but it's not on this I'm focus on now.

    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 1:53 PM
  • When you learn how to use the BPA you will find that it includes a detailed report of everything in your audit.


    \_(ツ)_/

    Friday, January 17, 2020 2:41 PM
  • Script uploaded with here string to correct display :)

    Now, display is not good on the end of my script but it's maybe because it's html ?


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    The end of your display is good as a "here" string is just a string so it would not be colorized.


    \_(ツ)_/

    Friday, January 17, 2020 2:42 PM
  • Now that your script can almost be copied easily I ran it on a couple of small domains and it seems to work OK.  

    What is it that you want added?  Most Admins run BPA as it is built into current versions of AD and contains all of the same information plus much more.  If there are extras to add to BPA then the BPA XML can be extended with a custom template an extra info can be attached.

    If this script allows you better focus then OK.  I would recommend posting int eh Directory Services forum to discuss what else you might add.  

    This forum is more oriented to asking questions about issues with PowerShell language and issues with creating a specific script.

    Maybe I am wrong and someone might catch on to what you are trying to do.

    Try placing your script in a file and posting to the Gallery as this might also get more attention and would allow users to directly criticize and suggest as the Gallery has its own Q&A page.


    \_(ツ)_/

    Friday, January 17, 2020 3:00 PM
  • mmmm I use BPA only with server manager and "launch bpa".

    You seem to say it can do lot of more O_o

    I tried this : https://www.jorgebernhardt.com/using-powershell-bpa-reports-html/

    it's really not enough. It's just to give miss configuration but not group member, active directory site list, network list, repadmin result, ...

    I make some fix issue because there are some error. Tombstone was empty and recycle bin too because of variable order.

    In the script, there is nothing about try / catch  error... I know lot of thing can be improved ^^

    I'll post my script on the gallery and in AD section here :)

    Thanks for your help.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.



    • Edited by matteu31400 Friday, January 17, 2020 3:14 PM
    Friday, January 17, 2020 3:06 PM
  • mmmm I use BPA only with server manager and "launch bpa".

    You seem to say it can do lot of more O_o

    I will try to find some more ressources about it :)

    I make some fix issue because there are some error. Tombstone was empty and recycle bin too because of variable order.

    In the script, there is nothing about try / catch  error... I know lot of thing can be improved ^^

    I'll try to look at BPA first and then post my script on the gallery and in AD section here :)

    Thanks for your help.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.


    https://docs.microsoft.com/en-us/windows-server/administration/server-manager/run-best-practices-analyzer-scans-and-manage-scan-results#BKMK_manage

    There is also a full site for BPA that documents all of the elements of a BPA and how to use them.

    The Server Manager is set to only show the summary results of the BPA and not the details.

    The BPAs have been around since about WS2003r2 and have evolved into a very comprehensive set of scans that analyze a subsystem to a very deep level.

    At a prompt:

    Get-BPAmodel | Invoke-BPAmodel


    \_(ツ)_/

    Friday, January 17, 2020 3:19 PM
  • Also you can generate the HTML directly but there is on deficiency.  You will have to add your own style sheet as the default has none.

    Just add a CSS to the following command.  You can generate each table as a fragment and use ConvertTo-Html to combine and style the tables.

    Get-BPAResult Microsoft/Windows/FileServices | 
         convertTo-Html | 
         Set-Content C:\BPAResults\FileServices.htm
    


    \_(ツ)_/

    Friday, January 17, 2020 3:27 PM
  • Yes, ok I see what you're talking about.

    It's good when you work with people who are "good" in AD or big company where all is already documented.

    I have customer with 2/3 DC and sometimes more like 10/15. They often don't know really how AD work (I'm not saying I'm really strong on it) and I need to collect information about the AD infrastructure to make word report.

    On the report I need to collect value about the actual configuration. All my function check 1 point or more.

    BPA don't give me member of the group. They don't say me FRS is used for sysvol replication. They don't say me tombstone value, who are FSMO roles, what network are put in AD sites and services and what are missing. How much time since the last update, .....

    I collect log of information in my script but I know it's not sufficient. I alreally collect eventviewer + BPA + some manual informations too ^^ cause I don't have time to improve the script everyday.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.


    • Edited by matteu31400 Friday, January 17, 2020 3:46 PM
    Friday, January 17, 2020 3:45 PM
  • Once you learn what is in the actual reports and how to display them you will find all of that is there.  Also the BPS does deep analysis and tells you if anything is wrong.  Look up all of the places where BPA is used and see how many companies have used BPA technology to create custom BPAs.

    If you are satisfied with your report then just use that but what needs to be audited is in the raw results produced by the bPA.  You need to create the raw HTML reports to see what needs to be audited.  How you get that info can also be deduced from the reports produced by BPA.

    Play with the BPA CmdLets for a while and you will begin to see how this works.

    Part of your issue is that you are calling a configuration report an audit.  An audit is a configuration, deployment and functional analysis of a system.  An audit is an analysis of the exceptions and not the raw configuration.

    Each subsystem has a tool that extracts the configuration but you have to first define all of the systems you want to analyze.  AD is a collection of systems that comprise a suite of functionality.  One of the best simple tools for displaying the info you want is call "dcdiag".  It can be optioned to almost any level of detail and also contains all of the info you are looking for as well as a report on errors and mis-configurations.

    Output from DCDIAG can be sent to an XML file which can be easily converted to an HTML report with a simple XSLT template.

    dcdiag /?

    Why reinvent the wheel.  Microsoft has been doing this for decades.  You will never catch up.


    \_(ツ)_/


    • Edited by jrv Friday, January 17, 2020 4:33 PM
    Friday, January 17, 2020 4:26 PM
  • Mmmmm I agree with you but not totally.

    Maybe I call an audit what it's not really.

    You said you launched my script, I supposed you see the output. You should agree with me, dcdiag is not sufficient for all what I check.

    My report launch a dcdiag and convert the result as an object. I find it on a blog and make some modification to be ok with french language.

    I don't said nothing already exist but there is not 1 utility to collect all that I collect with this report.

    I use some différents utility : dns, dhcp, repadmin, dcdiag, group member, hardware configuration, software installed, last update time, time source, service states, bitlocker enabled or not, firewall state, network settings, ntds.dit location, syvol and netlogon are shared or not, frs or dfsr is used for sysvol replication, disabled users numbers, ....there is not 1 utility to do this job.

    I make my own for this reason.

    What you try to say me is that I can implement my check for it to be integrated with BPA right ?

    I never worked with xml file and xslt and my job is not to develop. I make my script a lot on my free time....


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 4:47 PM
  • All that you mention is available in both reports.,  You just need tolearn how to option them to get that info available.  The XML outputs are easy to extract data from but can be more easily converted to HTML via an XSLT template.

    A little more research would help you find out how this can be used here and in many other places.  Once you master the generation of results you will see what I mean.


    \_(ツ)_/

    Friday, January 17, 2020 7:15 PM
  • what do you mean by both report ?

    I'm very interesting about it....


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 8:20 PM
  • Both = DCDIAG and BPA

    \_(ツ)_/

    Friday, January 17, 2020 8:49 PM
  • I don't know how is it possible to create custom BPA.

    When I search it on google, I find only RAP advertisment from microsoft to premier customers.

    Lot of website show GUI BPA and some other powershell to launch default BPA.

    What I think I have to find is what the comandlet do exactly but once again, I'm not developper and I don't know if commandlet source code is documented to find what the function execute exactly. I think it's on the framework, but maybe I'm totally wrong and if it's on the framework,  I don't know how to find it.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 9:42 PM
  • Only way I find is :Microsoft Baseline Configuration Analyzer

    But it's really long to do when I read documentation xD. I'm not sure if you are talking about it


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 10:00 PM
  • I posted a link to all of the CmdLets above.

    BPA is a set of letters used in many places.  The name is "Best Practices Analyzer"


    \_(ツ)_/

    Friday, January 17, 2020 10:05 PM
  • Yes, I know it's best practice analyzer :p

    Link with all comdlet documentation never say how to create custom BPA.

    It's explain how to use the 4 comandlet to launch BPA scan and result. The link I give you do the same. It launch BPA and display the result in html report.

    What I would need is to add rule for these BPA... but the commandlet want only existing categorie of the best practice (file server, ad, dns, ...)


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 10:20 PM
  • Yes, I know it's best practice analyzer :p

    Link with all comdlet documentation never say how to create custom BPA.

    It's explain how to use the 4 comandlet to launch BPA scan and result. The link I give you do the same. It launch BPA and display the result in html report.

    What I would need is to add rule for these BPA... but the commandlet want only existing categorie of the best practice (file server, ad, dns, ...)


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    You will have to stick with the CmdLets.  It would take you years to learn enough about systems programming to build a custom BPA which requires deep technical training in Windows OS technology.  Just generate eth XML and learn how to use an XSLT to generate HTML or extract teh data needed from the XML.

    Again - if your current code does what you want then keep it as is.  Unfortunately no one can address your original request because non one can know what your systems needs are.  The BPA reports wil show you what else is available.  Out side of that you are on your own.

    There are many third party tools that can do more by using databases and AI to extrapolate other information.  All of this is reeally beyond scripting for admins.


    \_(ツ)_/

    Friday, January 17, 2020 10:37 PM
  • OK, thanks :)

    Do you have some freetools to give me to test ?

    I already find some script in internet to collect different informations and I built my own because some informations are not present.

    Then some are free for non commercial use, so I can't use it on customer site.


    Merci de marquer comme reponses les interventions qui vous ont ete utile.

    Friday, January 17, 2020 11:37 PM
  • No.  I use BPA as it is since it does almost everything I can imagine.

    Most vendors will allow limited use for free so you can demo to your customer.  I have done this with many products and when i find a fit I have the customer order the license.    I have evaluated hundreds of tools of all kinds and get paid to write up evaluations and demo the tools.  Consulting is like that.  Every customer has custom needs and I have been useful in helping them find the right tools.  

    I don't promote any tool and try to choose at least three good tools to recommend.  In the end the customer has to make the choice with my providing assistance in making the choice.  We sit down and look at all aspects of all tools and let the customer choose between the strengths and weaknesses of the tools.

    That is the best you can do.


    \_(ツ)_/

    Saturday, January 18, 2020 12:22 AM