# Kleines ReportScript
# 20230209 AGAYL
# User Gruppenmitglidschaften auswerten und prüfen, ob das Passwort nach einem bestimmten Datum neu vergeben wurde
# Ausgabe in einer HTML Datei mit jQuery und ddtf.js
# Zellen werden farblich hinterlegt bei unterschiedlichen Eigenschaften
# Tabelle kann mit Hilfe von DDFT gefiltert werden
# TabellenKopf bleibt beim Scrollen oben fixiert
$SchwellDatum = [datetime]::ParseExact("18.01.2023","dd.MM.yyyy",$null)
# Alle Domains eines Forest holen
$domaincontroller = (Get-ADForest).Domains | %{ Get-ADDomainController -Filter * -Server $_ } | select Name,Domain | Sort-Object Domain -Descending | select domain -Unique
# In allen Domains die OUs heraussuchen, die dem Filterkriterium entsprechen
$ous =@()
foreach($domain in $domaincontroller.domain)
{
$ous += Get-ADOrganizationalUnit -LDAPFilter "(&(ObjectClass=OrganizationalUnit)(name=*ServiceAccount*))" -Server $domain | select DistinguishedName,@{n='domain';e={$domain}}
}
# Alle User unterhalb jeder OU der Domains in einen Array speichern und dabei entsprechende Attribute mit aufnehmen
$Users =@()
foreach($ou in $ous)
{
$Users += Get-ADUser -filter * -Properties memberof , description ,pwdlastset,Enabled -Server $ou.domain -SearchBase $ou.distinguishedName -SearchScope OneLevel | select samaccountname,@{n='domain';e={$ou.domain}},`
@{n='LogonAsService';e={$false}},`
@{n='LogonAsBatch';e={$false}},`
@{n='LogonLocalDeny';e={$false}},`
@{n='LogonRDPDeny';e={$false}},`
@{n='LogonlocalPrivileg';e={$false}},`
@{n='LogonRDPPrivileg';e={$false}},`
@{n='LogonTypeAcceptedStatus';e={$false}},`
@{Name='PASSWDNOTREQD' ;Expression={($_.UserAccountControl -band 32 ) -gt 0}} ,
@{Name='DONTEXPIREPASSWORD' ;Expression={($_.UserAccountControl -band 0x10000) -gt 0}},
@{Name='PASSWDNEVEREXPIRES' ;Expression={$_.passwordneverexpires } },
@{Name='PASSWDHAVETOCHANGE' ;Expression={( ConvertNTT $_.pwdLastSet) -eq 0 }},
@{n ="passwortgesetztAm" ;e ={([DateTime]::FromFileTime([Int64] $_.pwdlastset)) }} ,description,memberof,Enabled
}
# Liste von Gruppen nach denen ein Report erstellt werden soll:
$Gruppen =@("CN=LogonAsBatch,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
"CN=LogonAsService,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
"CN=LogonLocalDeny,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
"CN=LogonRDPDeny,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
"CN=LogonlocalPrivileg,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom",
"CN=LogonRDPPrivileg,OU=GPOContent,OU=Gruppen,DC=qwexx,DC=dom"
)
#Die Attribute der User aus der Liste bekommen nach der Liste der Gruppen nun ein Flag:
foreach($user in $Users)
{
if ($user.memberof -contains $Gruppen[0]) # hat die Liste MemberOf des Users einen Eintrag der gleich dem ersten Element des Array $Gruppen entspricht?
{
$user."LogonAsBatch" = $true
}
if ($user.memberof -contains $Gruppen[1])
{
$user."LogonAsService" = $true
}
if ($user.memberof -contains $Gruppen[2])
{
$user."LogonLocalDeny" = $true
}
if ($user.memberof -contains $Gruppen[3])
{
$user."LogonRDPDeny" = $true
}
if ($user.memberof -contains $Gruppen[4])
{
$user."LogonlocalPrivileg" = $true
}
if ($user.memberof -contains $Gruppen[5])
{
$user."LogonRDPPrivileg" = $true
}
if ( ($user.memberof -contains $Gruppen[5] -xor $user.memberof -contains $Gruppen[3] ) -and
($user.memberof -contains $Gruppen[4] -xor $user.memberof -contains $Gruppen[2] )
) # Entweder ist der User in der Verweigern- oder in der Privileg- Gruppe und das für RDP- und Lokale- Anmeldung
{
$user."LogonTypeAcceptedStatus" = $true # dann wird der zustand der Berechtigungen akzeptiert
}
$user.memberof = (($user.memberof) |%{$_}) -join "," # Gruppenmitgliedschaften in einen String bringen
######## START Bildschirmausgabe
Write-Host $("$($User.samaccountname)".PadRight(30) ) -ForegroundColor Cyan -NoNewline
######## ENDE Bildschirmausgabe
}
$Date = ((get-date -Format o).Split(".")[0]).Replace(':','')
if(!(Test-Path "$env:USERPROFILE\Desktop\Auswertungen\"))
{
New-Item -ItemType Directory "$env:USERPROFILE\Desktop\Auswertungen\"
}
$Users | Export-Csv -Delimiter ';' -Encoding Default -NoTypeInformation -Path "$env:USERPROFILE\Desktop\Auswertungen\AuswertungServiceAccountLogonAccess-$date.csv"
############# ############# ############# ############# ############# #############
############# Generiere HTML Report
############# ############# ############# ############# ############# #############
$datefile = Get-Date -UFormat '%Y-%m-%d-%H%M%S'
$reportfilepath = "$env:USERPROFILE\desktop\Auswertungen\ServiceAccount-$datefile-Report.html"
#ein XMLObjekt aus der Liste der User bauen. Leider habe ich die ID nicht als Attribute an der Tabelle nachträglich ändern können. Daher der unschöne "Replace"
[xml]$xmlObject = ($users |select samaccountname,domain,LogonAsService,LogonAsBatch,LogonLocalDeny,LogonRDPDeny,LogonlocalPrivileg,LogonRDPPrivileg,LogonTypeAcceptedStatus,passwortgesetztAm,description | ConvertTo-Html -Fragment ).replace("<table>","<table id=`"table_format`">")
# HTML CSS formatting
$head = @"
<style>
BODY{background-color:white;}
TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: LightBlue}
TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;foreground-color: black;background-color: white}
.green{background-color:#d5f2d5}
.blue{background-color:#e0eaf1}
.red{background-color:#ffd7de}
.yellow{background-color:#ffedac}
.durchgestrichen{text-decoration: line-through;background-color:#e0eaea}
thead {
position: sticky;
top: 0;
background: white;
text-align: left;
}
.filter {
position: absolute;
width: 20vw;
height: 30vh;
display: none;
text-align: left;
font-size: small;
z-index: 9999;
overflow: auto;
background: #ffffff;
color: #1f2d54;
border: 1px solid #dddddd;
}
.filter input {
margin: 5px !important;
padding: 0 !important;
width: 10%;
}
</style>
"@
for($i=1;$i -le $xmlObject.table.tr.Count-1;$i++)
{
$pwdLastSetText = ($xmlObject.table.tr[$i]).td[-2] #.'#text'
$enabled = $Users[ $Users.samaccountname.IndexOf( $xmlObject.table.tr[$i].ChildNodes[0].'#text')].enabled
# if( (New-TimeSpan -Start ($xmlObject.table.tr[$i]).td[-2] -End (Get-Date) ).Days -gt 26 ) #Feste Anzah an Tagen
if( ( [datetime]::ParseExact( $pwdLastSetText,"dd.MM.yyyy HH:mm:ss",$null) -le $SchwellDatum) )
{
$xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-2)].SetAttribute('class','yellow') # die Klasse bezieht sich auf das CSS
}
if($enabled -eq $false)
{
$xmlObject.table.tr[$i].ChildNodes[0].SetAttribute('class','durchgestrichen')
$xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-10)].SetAttribute('class','durchgestrichen')
$xmlObject.table.tr[$i].ChildNodes[($xmlObject.table.tr[$i].ChildNodes.Count-2)].SetAttribute('class','durchgestrichen')
}
## LocalLogonVerweigern
if( $xmlObject.table.tr[$i].td[4] -eq $true )
{
$xmlObject.table.tr[$i].ChildNodes[4].SetAttribute('class','green')
}
else
{
$xmlObject.table.tr[$i].ChildNodes[4].SetAttribute('class','red')
}
## RDP-LogonVerweigern
if( $xmlObject.table.tr[$i].td[5] -eq $true )
{
$xmlObject.table.tr[$i].ChildNodes[5].SetAttribute('class','green')
}
else
{
$xmlObject.table.tr[$i].ChildNodes[5].SetAttribute('class','red')
}
## Local-Privileg
if( $xmlObject.table.tr[$i].td[6] -eq $true )
{
$xmlObject.table.tr[$i].ChildNodes[6].SetAttribute('class','yellow')
}
else
{
$xmlObject.table.tr[$i].ChildNodes[6].SetAttribute('class','green')
}
## RDP-Privileg
if( $xmlObject.table.tr[$i].td[7] -eq $true )
{
$xmlObject.table.tr[$i].ChildNodes[7].SetAttribute('class','yellow')
}
else
{
$xmlObject.table.tr[$i].ChildNodes[7].SetAttribute('class','green')
}
## LogonTypeAcceptedStatus
if( $xmlObject.table.tr[$i].td[8] -eq $true )
{
$xmlObject.table.tr[$i].ChildNodes[8].SetAttribute('class','green')
}
else
{
$xmlObject.table.tr[$i].ChildNodes[8].SetAttribute('class','red')
}
}
#### Tabelle ist ab hier fertig
# kleine Statistik bauen
$hashTable =@{
AnzahlServiceAccounts = $Users.Count
RDPVerweigern = $($Users |?{$_."LogonRDPDeny"}).count
Lokalerweigern = $($Users |?{$_."LogonLocalDeny"}).count
Ausnahmen = $($Users |?{$_.LogonTypeAcceptedStatus}).count
PasswortZuAlt = $(($Users |?{ $_.passwortgesetztAm -le $SchwellDatum} ).count)
LogonTypeFailure = $( $Users.Count -$($Users |?{$_.LogonTypeAcceptedStatus}).count )
DavonDeaktivierteAccounts = $(($Users |?{$_.Enabled -eq $false}).count )
}
[xml]$xmlStatistik = [pscustomObject]$hashTable | select AnzahlServiceAccounts,DavonDeaktivierteAccounts,Lokalerweigern,RDPVerweigern,Ausnahmen,PasswortZuAlt,LogonTypeFailure | ConvertTo-Html -Fragment
# Define body and append the modified XML object
$body = @"
<div id="jquery-script-menu">
<div class="jquery-script-center">
<div class="jquery-script-clear">
</div>
</div>
</div>
<H2>ServiceAccount Status Report $datefile</H2>
$($xmlStatistik.InnerXml)
$($($xmlObject.innerxml).Replace("</th></tr>","</th></thead></tr>").Replace("<tr><th>","<tr><thead><th>"))
<script src=http://code.jquery.com/jquery-1.11.3.min.js></script>
<script>
(function($) {
$.fn.ddTableFilter = function(options) {
options = $.extend(true, $.fn.ddTableFilter.defaultOptions, options);
return this.each(function() {
if(`$(this).hasClass('ddtf-processed')) {
refreshFilters(this);
return;
}
var table = `$(this);
var start = new Date();
`$('th:visible', table).each(function(index) {
if(`$(this).hasClass('skip-filter')) return;
var selectbox = `$('<select class="form-control">');
var values = [];
var opts = [];
selectbox.append('<option value="--all--">' + `$(this).text() + '</option>');
var col = `$('tr:not(.skip-filter) td:nth-child(' + (index + 1) + ')', table).each(function() {
var cellVal = options.valueCallback.apply(this);
if(cellVal.length == 0) {
cellVal = '--empty--';
}
`$(this).attr('ddtf-value', cellVal);
if($.inArray(cellVal, values) === -1) {
var cellText = options.textCallback.apply(this);
if(cellText.length == 0) {cellText = options.emptyText;}
values.push(cellVal);
opts.push({val:cellVal, text:cellText});
}
});
if(opts.length < options.minOptions){
return;
}
if(options.sortOpt) {
opts.sort(options.sortOptCallback);
}
$.each(opts, function() {
`$(selectbox).append('<option value="' + this.val + '">' + this.text + '</option>')
});
`$(this).wrapInner('<div style="display:none">');
`$(this).append(selectbox);
selectbox.bind('change', {column:col}, function(event) {
var changeStart = new Date();
var value = `$(this).val();
event.data.column.each(function() {
if(`$(this).attr('ddtf-value') === value || value == '--all--') {
`$(this).removeClass('ddtf-filtered');
}
else {
`$(this).addClass('ddtf-filtered');
}
});
var changeStop = new Date();
if(options.debug) {
console.log('Search: ' + (changeStop.getTime() - changeStart.getTime()) + 'ms');
}
refreshFilters(table);
});
table.addClass('ddtf-processed');
if($.isFunction(options.afterBuild)) {
options.afterBuild.apply(table);
}
});
function refreshFilters(table) {
var refreshStart = new Date();
`$('tr', table).each(function() {
var row = `$(this);
if(`$('td.ddtf-filtered', row).length > 0) {
options.transition.hide.apply(row, options.transition.options);
}
else {
options.transition.show.apply(row, options.transition.options);
}
});
if($.isFunction(options.afterFilter)) {
options.afterFilter.apply(table);
}
if(options.debug) {
var refreshEnd = new Date();
console.log('Refresh: ' + (refreshEnd.getTime() - refreshStart.getTime()) + 'ms');
}
}
if(options.debug) {
var stop = new Date();
console.log('Build: ' + (stop.getTime() - start.getTime()) + 'ms');
}
});
};
$.fn.ddTableFilter.defaultOptions = {
valueCallback:function() {
return encodeURIComponent($.trim(`$(this).text()));
},
textCallback:function() {
return $.trim(`$(this).text());
},
sortOptCallback: function(a, b) {
return a.text.toLowerCase() > b.text.toLowerCase();
},
afterFilter: null,
afterBuild: null,
transition: {
hide:$.fn.hide,
show:$.fn.show,
options: []
},
emptyText:'--Empty--',
sortOpt:true,
debug:false,
minOptions:2
}
})(jQuery);
</script>
<script>
jQuery('#table_format').ddTableFilter();
</script>
"@
# Convert to HTML and save the file
(ConvertTo-Html -Head $head -Body $body) | Out-File $reportfilepath -Encoding utf8 # wenn nicht UTF8, dann funktioniert das JavaScript nicht