Thursday, February 20, 2020

Powershell: reporting on simple ldap DC connections

Every windows admin should be aware of what's coming March 2020:

https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirement-for-windows

The general recommendation at this point is to make this registry key adjustment to all your domain controllers:

# Enable Simple LDAP Bind Logging

Reg Add HKLM\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics /v "16 LDAP Interface Events" /t REG_DWORD /d 2

Once this has been done, you can monitor the event log on your DCs for event ID 2889 under the directory service log . . or you can run my script to check all your servers, and create a single report of all connections over the last 24 hours.

My script is based off the nice work by "Russell Tomkins" from Microsoft, his version available here:

https://github.com/russelltomkins/active-directory

The differences between our versions, his checks a single dc, mine checks the domain gets a list of DC's to check, then creates a report of all connections across them all.

The only parts to edit, are the lines for where to find the OU for he domain controllers, enter your domain name. And the path for the output CSV needs to exist as well.

hope it helps

**********start script***************

import-module activedirectory
cls
echo " "
echo " "
#
# Create shell arrays for holding the 2 needed data sets.
#
$Comps=@()
$Data=@()
#
# Gather list of Domain controllers
#
$Comps=get-adcomputer -filter * -searchbase "OU=Domain Controllers,DC=YOURDOMAINNAMEGOESHERE!!!,DC=com" | Sort Name
$compstocheck=$comps.count
#
# Gather data from each server's event logs, pull into single array.
#
echo " "
write-host "I found $compstocheck domain controllers, and will start checking their event data one by one" -foregroundcolor green
echo " "
ForEach ($DC in $Comps)
    {
        $dcname=$DC.name
        echo " "
        write-host "Pulling events from $dcname" -foregroundcolor Yellow
        echo " "
        $hours=24
        $Events=get-winevent -computername $dcname -filterhashtable @{Logname='Directory Service';Id=2889; StartTime=(get-date).AddHours("-$hours")} -ea silentlycontinue
        write-host "Processing events from $dcname" -foregroundcolor Cyan
        echo " "
        ForEach ($Event in $Events)
            {
$Etime=$Event.Timecreated
                $eventXML = [xml]$Event.ToXml()
                $Client = ($eventXML.event.EventData.Data[0])
                $IPAddress = $Client.SubString(0,$Client.LastIndexOf(":"))
                $Port = $Client.SubString($Client.LastIndexOf(":")+1)
                $User = $eventXML.event.EventData.Data[1]
                Switch ($eventXML.event.EventData.Data[2])
{
                        0 {$BindType = "Unsigned"}
                        1 {$BindType = "Simple"}
                    }
                $Row="" | select DCname,IPAddress,Port,User,BindType,TimeCreated
                $Row.DCname=$dcname
                $Row.IPAddress=$IPAddress
                $Row.Port=$Port
                $Row.User=$User
                $Row.BindType=$BindType
                $Row.TimeCreated=$ETime
                #
# Add the found event data to the master array
                #
                $Data +=$Row
            }
        write-host "Completed processing all related events for $DCname, moving on" -foregroundcolor Green
        echo " "
        }
#
#
write-host "Generating report" -foregroundcolor Green
echo " "
$Reportcsv="C:\SimpLdap\SimpleLdapReport.csv"
#
$Data | export-csv "$Reportcsv" -notypeinformation
#
*************End Script***********

Wednesday, July 24, 2019

Powershell - Arrays and how they handle multi-lined input data

For years with various methods of scripting I've often fallen to using temporary files as a means to store and retrieve the information my scripts were working on. I've always felt it was a 'poor mans' solution that any good scripter would look to avoid as much as possible.

So as I've spent more time with powershell recently, I've been making an effort to move away from temporary files, and trying to use powershell's hash table and array functions to store and retrieve the scripted info I need.

I had a case, where I needed to create a report and the data I was gathering was seemingly going into my array the same way that it was being displayed in my console...but everytime I went to extract the data I had put into my arrays, the returned info in code was junk and my reports never built.

After much trial and error I found 2 ways around my problem, the first was modifying export-csv command within my script to update my data as it was extracted so it would display properly .. . the second was "cleaning" my data before i imported it into the array . .and that's what I want to discuss with this post.

But first, the problem that powershell attempts to help you with.

You have a text file, with the following data within it:

the quick brown fox
jumped over the slow
brown cow who ate grass
with ducks by the pond

This is multi-lined data, here's how powershell displays this info once it's been pulled into an array:



Where did those comma's come from?

Did the text data, have comma separators?  Hmm..Looks like no...


This would be an example of powershell "adapting" your content, because it realized as it was ingesting data that was 'multi-line' so it had to adjust it so it could work with it within the array framework.

Line separations are defined within the array as "commas" and the entire set of data is bracketed "{}".

And so the question becomes, once you know this limitation of powershell arrays exist, how do you plan around it?

For me, the "best" way around this is going back to what I know well . . LOL . . using text files as temporary staging areas to "clean" the data the I'm importing into my arrays so that it can be exported cleanly with export-csv and no special tricks.

"Data cleaning of the txt file" in this case, means removing empty lines, adding semi-colons to the end of each line, and merging all lines into a single line . . . the commands that help with those are:

Remove empty lines:

(GC $txtfile) | foreach {$_.trimend()} | where {$_ -ne ""} | sc $txtfile

Adding semi-colons:

(gc $txtfile) | foreach {$_ + ";"} | sc $txtfile

Turning all lines into one line for array import (2 liner):

$txtarrayimport=gc $txtfile
$DataToImport=$txtarrayimport -join ""

This may seem like an extreme amount of effort, but it's been extremely important lesson for me in terms of knowing that powershell can and will adjust your data on it's own. Taking the time to import data into powershell arrays in a format it can handle better can mean a big difference in exporting that data later.




Friday, November 16, 2018

Powershell - lessons learned when trying to document ADSI permissions...

I had a need to document permissions assigned from within ADSI to a configuration container with hundreds / thousands of objects beneath.

Powershell surprisingly treats active directory much like the file system with regards to it's object references when you're looking for access rights information.

What I couldn't find was a good reference for how to recursively report on permissions under Active Directory's configuration area (think MS Exchange).

Below is my first attempt at code to dump all CN / OU permissions. Note that AD pathing is important, and you'll want to edit the $config line to the path you want to report on.

****************************Begin Script******************************************

Import-module activedirectory
$Temp="c:\temp\test.txt"
$config=get-childitem -recurse -path "AD:CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=YourDomain,DC=com" | select -expandproperty DistinguishedName
Foreach ($object in $config)
{
$Path="AD:" + $object
$object >>$Temp
(get-acl $path).Access | Where {$_.IsInherited -eq $FALSE}|select InheritanceType,AccessControlType,IdentityReference,IsInherited >>$Temp
echo " ">>$Temp
}

****************************End Script ********************************************

This code, got the job done, it documented my permissions, but it created a fairly large Txt file which was a bit of a chore to sift through. It can be done better. . .

However, lets talk about what is going on with what we have first. . . If you're following along in the script, the data returned from the $config query is the full path to the AD object we want to query. But we can't just give that path to the "get-acl" command, it won't take it. Instead, we have to pre-pend the "AD:" to the query for get-acl, so a separate variable is used to turn our $config path into a value that get-acl can actually query and report on.

Also note, the data set is trimmed to only return directly assigned permissions. Any "inherited" permissions won't be shown as that makes for a massively sized report.

So I had results, but I wasn't happy with how it was presented, so I thought a bit about how I was getting at the data I wanted, and how it was being expressed . . and I realized something that I feel is very important to understand with powershell . . .

I am using a command in "get-acl" which returns multiple values. I point it at a folder / file / adobject, and it gives me a response that I can further filter to get only the data I want . . but I have to tinker with powershell a bit to do it...in my first code attempt no filtering was being done of the get-acl data, I was just displaying it as powershell would present it . . to a text file.

Here's another attempt at the same task, but filtering the get-acl query into specific values that a report is then built from:

**********************Begin Script****************************************

Import-module activedirectory
$Temp="c:\temp\test.txt"
$DataSet=get-childitem -recurse -path "AD:CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=YourDomain,DC=com" | select -expandproperty DistinguishedName
echo "CN Path;Group;Allow-Deny;Permissions;Inherited">$Temp
Write-host "Gathering permissions data..." -foregroundcolor Green
Foreach ($object in $DataSet)
{
$Path="AD:" + $object
$PermCheck=(get-acl $path).Access | Where {$_.IsInherited -eq $FALSE} | select ActiveDirectoryRights,AccessControlType,IdentityReference,IsInherited
Foreach ($Perm in $PermCheck)
{
$ADR=$perm.activedirectoryrights
$ACT=$perm.accesscontroltype
$IR=$perm.IdentityReference
$II=$perm.isinherited
echo "$object;$IR;$ACT;$ADR;$II">>$Temp
}
}
echo " "
Write-host "Data dump completed, finalizing report..." -foregroundcolor Green
$csv="c:\temp\MSAD-Perms-Config-Exchange.csv"
import-csv $Temp -delimiter ";" | export-csv $csv -NoTypeInformation
$ReportXLSX="c:\temp\MSAD-Perms-Config-Exchange.xlsx"
$vbscript="\\server\networkshare\CSV-Convert\csv_to_excel.vbs"
& $vbscript $csv $reportxlsx
timeout /t 5 /nobreak
echo " "
write-host "Report created" -foregroundcolor Green
echo " "

********************End Script*******************************************

Following along in the code here, I've changed the "get-acl" into a variable, then added a foreach section to make new variables out of each of the specific values I want to see queried from each permission. Then, only those values are "echo'd" to my waiting temp text file. The data sent to the txt file is separated with a ';' for importing with import-csv later in the script. I also referencing some code I've leveraged in the past on my blog in other postings, to convert a CSV file into a pre-formated XLSX file.

The result is a clear break-out of any and all permission assignments for an AD structure. The more I think about this, it feels like it wouldn't take much to convert this same code to reporting on file system permissions. I may test that out in another blog posting . . . ;)

Hope this helps others-

Thursday, September 20, 2018

Powershell - quickly stopping all skype services

For some reason, there's very little out there on a quick way to find and stop all skype services (this is now needed when running skype updates.)

Skype has it's own powershell commandlets for doing a variety of administration for skype systems, in this case we're going to leverage a single line command to get and stop all services:

*****script start*****

get-cswindowsservice | stop-cswindowsservice

*****script end*****

I have seen cases where this will simply hang out because the services themselves are locked up. In those cases, you either need to wait out the service stopping, or force a reboot and try again after your server is back online.

Tuesday, October 17, 2017

Forcing password resets for a group of users

Management is always good for coming up with requests that are . . .unique.

In today's case, my management was planning to bring extra staff in to "help" with a push to force all staff members to change their network passwords. The initial plan on this was that these 'extras' would help by personally clicking through user accounts and flagging them to 'change password on next login'.

"Why can't we just script it?" I asked.

"Well we can't just set everyone to change their password we need to be able to exclude specific staff, and only force specific accounts to be forced to change. ". I was told in response.

"No problem..." I say.

There are plenty of samples out there for using command line and powershell commands to flag all members of an OU, or all users in a domain to have their passwords set to change on next logon, but I was unable to find what I needed to allow us to be more selective.

What I needed was a way to set "change password on next logon" for select staff quickly and with reporting. I decided to base my process on a windows group. Here's the script result and what it does.

- Checks the members of a group in AD, dumps that member list to a text file.
- Runs through the resulting list of names and sets them to change password on next login. 

*****Begin Script*****

# Output Variables
$GM = "c:\scripts\groupmembers.txt"
$results = "c:\scripts\changerequests.txt"

# Add AD module for queries
import-module ActiveDirectory

# Dump group members
get-adgroupmember -identity ForcePwdChange | select Name | Sort Name >$GM

# Prep results file
echo " ">$results

# Clean up group members data dump
(gc $GM | select -Skip 3) | sc $GM
$Lines = (gc $GM)
$Lines | ForEach-Object { $_.TrimEnd(); } | Out-File $GM -Encoding Ascii
(gc $GM) | ? {$_.trim() -ne "" } | sc $GM

# Process the list of names
$List = (gc $GM)
Foreach ($U in $List)
{
get-aduser -filter 'Name -like $U' | set-aduser -changepasswordatlogon:$true
echo "$U set to change password on next login" >>$results
echo "$U PWD set to change on next Logon"
echo " "
}
read-host "press any key to exit"

*****End Script*****

I need to do a bit of clean up with adding some error catching to this, but I'm posting it now to get it out there. ;) 

I've worked before with parsing the user lists from AD groups, and the sub-section included here for 'clean up group members data dump' is essential, because otherwise the user names won't get clearly read by powershell for the other actions we need. 

This solution is entirely situational . .but that's what scripting is meant to help with, situational issues. Hopefully this helps someone else out there. 





Thursday, November 10, 2016

Powershell - print all files from a directory

This likely doesn't come up very often for folks these days, but someone out there may appreciate this script.

This script came from a business need to print off a few hundred text files from a directory on the network. With windows printers, you can drag files to a printer and "drop" them into it, to be able to print a lot of files at once, but there's still quite a bit of clicking involved.

This script when run, will prompt for selecting a directory from mapped drives on the machine, it will then print all contents of that directory to the default printer for the machine.

I could get fancier with this, but don't feel a need to as yet. Hopefully this may serve to help others as is / with some minor modifications.

**********Begin script***********

cls
echo " "
echo " "
write-host "Would you like to Print all the Txt files from a directory?" -foregroundcolor RED
echo " "
$Selection = read-host "Type y or n:"
echo " "
cls
If ($Selection -eq "y")
{
cls
Write-Host "Select where to pull the Txt files from..." -ForegroundColor yellow
function Read-FolderBrowserDialog([string]$Message, [string]$InitialDirectory)
{
$app = New-Object -ComObject Shell.Application
$folder = $app.BrowseForFolder(0, $Message, 0, $InitialDirectory)
if ($folder) { return $folder.Self.Path } else { return '' }
}
$directory = Read-FolderBrowserDialog ("Select the folder to print Txt files from","C:\")
$PrintFiles = Get-childItem $directory
ForEach ($PrintFile in $PrintFiles)
{
Start-Process -FilePath "$directory\$PrintFile" -Verb Print -PassThru |%{sleep 5;$_}|kill
echo " "
write-host "Printing $PrintFile..." -foregroundcolor Green
echo " "
}
echo " "
Write-host "All files from the selected directory have been Printed" -foregroundcolor Green
echo " "
read-host "Press enter to exit:"
}
ELSE
{
echo " "
write-host "You chose no, this script is exiting..."
echo " "
read-host "press enter to exit:"
}

**********End Script*******************

Tuesday, July 26, 2016

Powershell - Logon hours value comparisons

Time to post an update to my widely un-read blog. :)

I had a case where management was requesting a check on the LogonHours value for staff members, in particular they wanted to know specific users who had a specific logonhours setup, but they also wanted to track the staff who either had no logonhours config, or could logon at all hours.

A quick google will bring up many many hits about how to user powershell to "SET" your logonhours values, and there's a really nice script from Richard Mueller "http://www.rlmueller.net/PowerShell/PSAllUsersLogonHours.txt" that can be used to make a report of current settings.

But there was nothing I could personally find regarding "comparing" / or really "confirming" that logon hours are set as management desires. This value in AD is stored oddly, so many scripts devolve into trying to parse the data apart into more readable formats . .that's all well and good, but a waste of time for my purposes. So I decided I needed to find my own way on this . .

I will post my finished script shortly, but want to first clarify the steps I used:

$Default = get-aduser BaseUser -properties *
$DefaultCheck = $Default.logonhours
By breaking variables out in the above manner, we get a variable which is specifically a logonhours value for a user account we want to use as baseline for comparisons in the script. (Note that BaseUser should be the samaccount name you want to check against in AD.)
 Again, lots of folks want to break down this data into a readable format . .that's great, but I don't honestly care, I want to compare a 'correct' setting against other accounts, and build a report, to do that....:

$Compare1 = "$Userhours" -match "$DefaultCheck"
More variables, but this line is our "comparison" by using -match. The return is a "True" "False" which can be further coded against. (Note $Userhours in the above case is a variable similar to $DefaultCheck, it's the user logon values we've pulled from AD and want to compare to our baseline value).

So with the above clarified, the remaining logic for the script is:


  • Identify the AD accounts in AD to be used for LogonHour comparisons.
  • Decide how you want the data reported.
For my particular case, my management had the following requests for the report:
  • list the user, list the user's manager, list the logon hours setup.
  • a spreadsheet would be nice.
For my LogonHours comparisons, I had:
  • 1 user logonhour value that could be used as my Corporate baseline.
  • 1 user logonhour value that could be used as my baseline for AllHours access.
  • a catch routine for tracking users that had "NULL" as their logonhours value.
    • this means it was never defined in AD, and is effectively "AllHours access".
  • a catch group for tracking users that didn't match any of the above for their logonhours value.
For the Excel file report generation:
  • I tend to favor spreadsheets with autoformatting, in order to get such an output from my scripts I will often place a call to a VBS subroutine from: http://jeffkinzer.blogspot.com/2010/06/vbscript-to-convert-csv-to-xlsx.html
    • Jeff's very handy "csv_to_excel.vbs" can be easily called from within powershell to create autoformatted spreadsheets. 
The below script, will require a few edits to work in another environment, so pay attention to variable declarations. And as always, use at your own risk . .TEST TEST TEST.

Powershell script example:



Import-module activedirectory

#define variables for reports
$Report = "c:\Reports\Fulllist.txt"
$Nulls = "c:\Reports\Users-with-Null.txt"

#define logon hours results to query against
$Default = get-aduser UserNameVariable -properties *
$DefaultCheck = $Default.logonhours
$Allhours = get-aduser UserNameVariable -properties *
$AllhoursCheck = $Allhours.logonhours

#Pull user domain list
$Users = get-aduser -filter 'enabled -eq $true' -properties * -searchbase "LDAP domain search string variable for example DC=,DC=,DC=com"

#Prep reports with data column names
echo "Name ; LogonHours ; Manager" >>$Report

#Sort through results
ForEach ($User in $Users)
{
$Name = $user.name
$UserHours = $user.logonhours
$Manager = $user.Manager
#Check if logon hours is Null or not
If ($user.logonhours -eq $Null)
{
If ($Manager -eq $Null)
{
echo "$Name ; NotSet ; NotSet">>$Report
}
ELSE
{
$ManQuery = get-aduser $Manager
$ManName = $ManQuery.Name
echo "$Name ; NotSet ; $Manname">>$Report
}
}
ELSE
{
#Compare users logon hours with default.
$Compare1 = "$Userhours" -match "$DefaultCheck"
If ($Compare1 -eq $True)
{
If ($Manager -eq $Null)
{
echo "$Name ; Default ; NotSet">>$Report
}
ELSE
{
$ManQuery = get-aduser $Manager
$ManName = $ManQuery.Name
echo "$Name ; Default ; $Manname">>$Report
}
}
#Computer users logon hours with All hours authorization.
$Compare2 = "$Userhours" -match "$AllhoursCheck"
If ($Compare2 -eq $True)
{
If ($Manager -eq $Null)
{
echo "$Name ; AllHours ; NotSet">>$Report
}
ELSE
{
$ManQuery = get-aduser $Manager
$ManName = $ManQuery.Name
echo "$Name ; AllHours ; $Manname">>$Report
}
}
ELSEIF ($Compare1 -eq $False)
{
If ($Manager -eq $Null)
{
echo "$Name ; Variant ; NotSet">>$Report
}
ELSE
{
$ManQuery = get-aduser $Manager
$ManName = $ManQuery.Name
echo "$Name ; Variant ; $Manname">>$Report
}
}
}
}

write-host "report generation completed"
write-host "converting to XLSX"
$CSV = "c:\reports\Logonhours.csv"
$XLSX = "c:\reports\LogonHours.xlsx"

import-csv $Report -delimiter ";" | export-csv $csv -NoTypeInformation
$vbscript = "c:\csv-convert\csv_to_excel.vbs"
& $vbscript $Csv $xlsx
timeout /t 5 /nobreak
Remove-item $CSV -ea silentlycontinue
exit