Powershell Active Directory Get Expired Users - powershell

this is my first time on Stack Overflow so please have mercy :)
Im trying to create a Powershell GUI to do a search request on our Active directory that shows me all expired and soon expiring User Accounts. I tried it with the following function.
I get an syntax error in my request (Get-ADUser)...
Error Message in Powershell ISE
$ShowExpiring.Add_Click({
$ADUserSearch.Visible = $False
$CheckDisabled.Visible = $False
$ShowExpiring.Visible = $False
$Back.Visible = $True
$Results.Visible = $True
$Results.Clear()
$Results.ScrollBars = "Vertical"
Import-Module ActiveDirectory
$CurrentDate = Get-Date
$ExpiringPasswords = Get-ADUser -Filter '((PasswordExpired -eq $True) -or (PasswordLastSet -le ((get-date).AddDays(-((get-addefaultdomainpolicy).MaxPasswordAge.Days)))))' -Properties Name,PasswordLastSet
if($ExpiringPasswords) {
$ExpiringPasswords = $ExpiringPasswords | sort PasswordLastSet
foreach ($User in $ExpiringPasswords) {
if ($User.PasswordLastSet -lt (Get-Date).AddDays(-((Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days))) {
$Results.SelectionColor = "Red"
else {
$Results.SelectionColor = "Orange"
}
$Results.AppendText("Username: $($User.Name) Expiration Date: $($User.PasswordLastSet)`n")
}
else {
$Results.AppendText("No passwords expiring or already expired.")
}
})
I also tried it with this code which gives me no error message but also no result from disabled users:
$ShowExpiring.Add_Click({
$ADUserSearch.Visible = $False
$CheckDisabled.Visible = $False
$ShowExpiring.Visible = $False
$Back.Visible = $True
$Results.Visible = $True
$Results.Clear()
$Results.ScrollBars = "Vertical"
Import-Module ActiveDirectory
$CurrentDate = Get-Date
$ExpiringPasswords = (Search-ADAccount -AccountExpired -UsersOnly | select Name, #{n='ExpirationDate';e={[datetime]::FromFileTime($_.AccountExpirationDate)}}) + (Search-ADAccount -AccountExpiring -TimeSpan (New-TimeSpan -Days 10) -UsersOnly | select Name, #{n='ExpirationDate';e={[datetime]::FromFileTime($_.AccountExpirationDate)}})
if($ExpiringPasswords) {
$ExpiringPasswords = $ExpiringPasswords | sort ExpirationDate
foreach ($User in $ExpiringPasswords) {
if ($User.ExpirationDate -lt $CurrentDate) {
$Results.SelectionColor = "Red"
else {
$Results.SelectionColor = "Orange"
}
$Results.AppendText("Username: $($User.Name) Expiration Date: $($User.ExpirationDate)`n")
}
else {
$Results.AppendText("No passwords expiring or already expired.")
}
})
Thank you for helping me.

The reason for your syntax error is likely the fact that you are trying to use
Get-ADDefaultDomainPolicy
...which does not exist. What you're looking for is
Get-ADDefaultDomainPasswordPolicy
Here is some code that you should substitute in the appropriate place in your first example. I broke things down a little bit to make the code/filter easier to understand, but you can recombine it if you are so inclined to do so.
$MaxPasswordAgeDays = $(Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.Days
$OldestAcceptablePasswordLastSetDate = $(Get-Date).AddDays(-$MaxPasswordAgeDays)
$ExpiringPasswords = Get-ADUser -Filter {PasswordExpired -eq $True -or PasswordLastSet -le $OldestAcceptablePasswordLastSetDate} -Properties Name,PasswordLastSet
I would suggest using { } instead of single quotes ' ' so that your Powershell editor can help you with intellisense and syntax highlighting rather than the single quotes in your example. Aside from that, if you ever encounter syntax errors, I would recommend trying to break it down as I did above to help you understand which part of your code (in this case, your filter) is failing. I discovered rather quickly that you were trying to use a non-existent cmdlet by doing so.

Related

Powershell: Count within foreach loop

I have a Powershell script that queries AD user certificates. I'd like to check and inform the user if his VPN certificate is going to expire soon. The script runs fine so far, but I have some instances where the user already has 2 VPN certificates and I don't want to notify if this is the case (in Script on step "### Execute next steps if count is less then 2"). I've tried to add ".count" to some of the variables, but since it's within the foreach, it's always giving me a "1" as a match. I have no clue how to achieve this, please help. Here's the script:
param (
[string]$queryDN = '...DC=com',
[string]$VPNcertname = 'VPN OID',
[int]$days = 14
)
# Make decision what to check
$userpath = $queryDN
$cert2check = $VPNcertname
# Begin script
$users = (Get-ADGroupMember -Identity $userpath).distinguishedName
foreach ($dude in $users) {
$user = Get-ADUser $dude -Property Certificates
$Certificatelist = $user.Certificates | foreach {
New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $_
}
ForEach($cert in $Certificatelist){
$querycertificate = if($cert.EnhancedKeyUsageList.Where({$_.FriendlyName -eq $cert2check})){
### Execute next steps if count is less then 2
$expirationDate = $Cert.NotAfter
if ($expirationDate -lt [datetime]::Today.AddDays($days)) {
write-host The $cert2check certificate for user: $user.UserPrincipalName`, expires: $expirationDate. This is less than: $days days and should be changed soon.
}
}
}
}
Thanks
Thanks #Vesper. This was exactly what I was looking for:
$CertList2=$Certificatelist|where {$_.EnhancedKeyUsageList.FriendlyName -eq $cert2check}; if ($CertList2.count -gt 1) {...}

Powershell Send an email if success or if error

I have a script that looks at a CSV, filters the email address and performs some tasks:
foreach($aduser in (import-csv "C:\Temp\users.csv")){
Get-ADUser -filter "emailaddress -eq '$($aduser.emailaddress)'"|
Set-ADobject -ProtectedFromAccidentalDeletion $false
Get-ADUser -filter "emailaddress -eq '$($aduser.emailaddress)'" |
Set-ADUser -Enabled $false
Get-ADUser -filter "emailaddress -eq '$($aduser.emailaddress)'" |Move-ADObject -TargetPath "OU=Sep 20,OU=Disabled User Accounts,DC=Mydomain,DC=Domain,DC=uk" -PassThru | Disable-ADAccout}
If ($Error){
$FailMailParams = #{
To = 'Example#Gmail.com'
From = 'Example#Gmail.com'
SmtpServer = 'My.smtp.server'
Subject = 'Script Errors Out'
Body = 'There was an error with the script!!'
}
Send-MailMessage #FailMailParams
} else {
$SuccessMailParams = #{
To = 'Example#Gmail.com'
From = 'Example#gmail.com'
SmtpServer = 'My.smtp.server'
Subject = 'Success'
Body = 'The script ran successfully'
}
Send-MailMessage #SuccessMailParams
}
The problem I am facing is that even if the script runs successfully I still get the error email. If I change the code to if ($error -eq 1) and the code errors out I get the successful email. I think it's the If ($error) Variable causing the issue but I don't know what to use?
Error control can be handled in different ways. In your code, you have nothing that is indicating $error that should be set to 1.
I suggest you to find some examples of how to use the Try-Catch structure, as you will have to use it a lot in Powershell.
Find an example below which should be easy to adopt for your already made code:
try
{
# Code that performs the work intended
}
catch
{
# What to do in case any error is raised from above Try section
$error = 1
}
finally
{
# Here you post code that will be executed no matter errors happened or not,
# for example your email definition and execution.
}
Said this, your code has more room for improvement, but if you are starting with it, let's not focus on that part still.

Missing AD module and can't get it, need something similar or something to simulate it

So I'm trying to output a complete KB list for all computers on a server (which works on one computer) but it doesn't recognize Get-ADcomputer as a cmdlet. When checking various sources, it appears that the AD module isn't included. As I'm doing this on a work computer/server I'm hesitant to download anything or anything of that nature.
Is there any way I can achieve the following without using the AD module or someway I might be missing how to import the module (if it exists, which I don't think it does on this system)?
# 1. Define credentials
$cred = Get-Credential
# 2. Define a scriptblock
$sb = {
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object -Process {
$Title = $null
if ($_.Title -match "\(KB\d{6,7}\)") {
# Split returns an array of strings
$Title = ($_.Title -split '.*\((?<KB>KB\d{6,7})\)')[1]
} else {
$Title = $_.Title
}
$Result = $null
switch ($_.ResultCode) {
0 { $Result = 'NotStarted'}
1 { $Result = 'InProgress' }
2 { $Result = 'Succeeded' }
3 { $Result = 'SucceededWithErrors' }
4 { $Result = 'Failed' }
5 { $Result = 'Aborted' }
default { $Result = $_ }
}
New-Object -TypeName PSObject -Property #{
InstalledOn = Get-Date -Date $_.Date;
Title = $Title;
Name = $_.Title;
Status = $Result
}
} | Sort-Object -Descending:$false -Property InstalledOn | Where {
$_.Title -notmatch "^Definition\sUpdate"
}
}
#Get all servers in your AD (if less than 10000)
Get-ADComputer -ResultPageSize 10000 -SearchScope Subtree -Filter {
(OperatingSystem -like "Windows*Server*")
} | ForEach-Object {
# Get the computername from the AD object
$computer = $_.Name
# Create a hash table for splatting
$HT = #{
ComputerName = $computer ;
ScriptBlock = $sb ;
Credential = $cred;
ErrorAction = "Stop";
}
# Execute the code on remote computers
try {
Invoke-Command #HT
} catch {
Write-Warning -Message "Failed to execute on $computer because $($_.Exception.Message)"
}
} | Format-Table PSComputerName,Title,Status,InstalledOn,Name -AutoSize
You've got 3 options:
First is to just install the RSAT feature for AD which will include the AD module. This is probably the best option unless there is something specific preventing it. If you're running your script from a client operating systems you need to install the RSAT first, though.
Option 2 (which should only be used if adding the Windows feature is somehow an issue) is to download and use the Quest AD tools, which give very similar functionality, but it looks like Dell is doing their best to hide these now so that may be difficult to locate...
Option 3 is to use the .NET ADSI classes to access AD directly, which will work without any additional downloads on any system capable of running PowerShell. If you'd like to go this route you should check out the documentation for the interface Here and for the System.DirectoryServices namespace Here.
Edit
Just noticed the last part of your question, what do you mean by "a complete KB list"? Not just Windows updates or things updated manually or whatever? What else would be in a list of Windows updates that was not a Windows update?
You have not mentioned the OSes you are using but in general if you have a server 2008 R2 or above, all you have to do it activate the RSAT feature AD PowerShell Module and you will have the cmdlet you are looking for.
On a client machine, you 'have to' install RSAT, and then activate the features. You can take a look at the technet article for more info: https://technet.microsoft.com/en-us/library/ee449483(v=ws.10).aspx
If you don't want to use that option, then you will have to use .NET ADSI classes. There are tons of examples on how to do this, it basically boils down to a couple of lines really. Technet has examples on this as well: https://technet.microsoft.com/en-us/library/ff730967.aspx

AD user creation with SamAccountName availability check loop

I'm learning Powershell by making a script that will hopefully automate everything that needs to be done when we get a new hire or a consultant. Currently I'm working on the part that will create the AD account. Below are the variables specific to this portion of the script.
#Variables for preliminary SamAccountName, modifiers and initials array
$PrelSamAccountName = ("se" + [string]$GivenName.Substring(0,3) + [string]$SurName.Substring(0,3)).ToLower()
$Modifier1 = 1
$Modifier2 = 0
$InitialsArray = #("x","y","z")
Here is the loop. I cut out a bunch of parameters on New-ADUser to make it less cluttered.
try {
#Checks if preliminary SamAccountName is taken or not
$ADCheck = Get-ADUser -Filter {SamAccountName -eq $PrelSamAccountName}
#Creates new user
New-ADUser -Name $Name -SamAccountName $PrelSamAccountName
} catch {
#Replaces final character in preliminary SamAccountName with "1++"
while (($ADCheck | Select-Object -ExpandProperty SamAccountName) -eq $PrelSamAccountName) {
$PrelSamAccountName = ([string]$PrelSamAccountName.Substring(0,7) + ($Modifier1++)).ToLower()
}
#Changes $Initials from $null to x/y/z if an existing user has identical name as new user
while (($ADCheck | Select-Object -ExpandProperty Name) -eq $Name) {
$Initials = $InitialsArray[$Modifier2++]
$Name = $GivenName + " " + $Initials + " " + $SurName
}
}
Everything is working as intended, except for the fact that a new user is created every other time I run the loop. Ideally I would want it to create a new user every time it is run. :)
I'm assuming it has something to do with the placement of the $ADCheck variable, but after having rewritten this portion multiple times I simply can't get it to work. Any ideas?
Thanks in advance.
You have some logical problems in here:
Follow this Approach:
Pseudocode:
while (user exist) {create new username}
new-aduser new username
PS Code:
function new-sam
{
#creates new sam
}
$sam = "username"
$a=$false
while (!$a)
{
try {
$a = [bool](Get-ADUser -Identity $sam )
}
catch {
$a = $false; $sam = new-sam
}
}
new-aduser ....

Powershell - match with Containskey & set value of hashtable don't work

I am working on a script by Richard L. Mueller to disable inactive account in our AD.
Trap {"Error: $_"; Break;}
$D = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
$Domain = [ADSI]"LDAP://$D"
$Searcher = New-Object System.DirectoryServices.DirectorySearcher
$Searcher.PageSize = 200
$Searcher.SearchScope = "subtree"
$Searcher.Filter = "(&(objectCategory=person)(objectClass=user))"
$Searcher.PropertiesToLoad.Add("samAccountName") > $Null
$Searcher.PropertiesToLoad.Add("lastLogon") > $Null
$Searcher.PropertiesToLoad.Add("accountExpires") > $Null
# Create hash table of users and their last logon dates.
$arrUsers = #{}
# Enumerate all Domain Controllers.
ForEach ($DC In $D.DomainControllers)
{
$Server = $DC.Name
$Searcher.SearchRoot = "LDAP://$Server/" + $Domain.distinguishedName
$Results = $Searcher.FindAll()
#$Results[100].Properties.item("samAccountName")
#$Results[100].Properties.item("lastlogon")
ForEach ($Result In $Results)
{
$DN = $Result.Properties.Item("samAccountName")
$LL = $Result.Properties.Item("lastLogon")
If ($LL.Count -eq 0)
{
$Last = [DateTime]0
}
Else
{
$Last = [DateTime]$LL.Item(0)
}
If ($Last -eq 0)
{
$LastLogon = $Last.AddYears(1600)
}
Else
{
$LastLogon = $Last.AddYears(1600).ToLocalTime()
}
If ($arrUsers.ContainsKey("$DN"))
{
If ($LastLogon -gt $arrUsers["$DN"])
{
$arrUsers["$DN"] = $LastLogon
}
}
Else
{
$arrUsers.Add("$DN", $LastLogon)
}
}
}
Now I have the most updated LastLogon date of my AD users.
Then I do:
Foreach ($ou in $searchRoot) {
$inactiveUsers += #(Get-QADUser -SearchRoot $ou -Enabled -PasswordNeverExpires:$false -CreatedBefore $creationCutoff -SizeLimit $sizeLimit | Select-Object Name,SamAccountName,LastLogonTimeStamp,Description,passwordneverexpires,canonicalName | Sort-Object Name)
}
I do not use this to disable the ID because LastLogonTimeStamp has a delay being updated from 9-14 days. And with the real last logon date in $arrUsers, I would like to replace LastLogonTimeStamp with it. So I want to match them using the user ID:
Foreach ($inuser in $inactiveUsers) {
If ($arrUsers.ContainsKey("$inuser.samAccountName"))
{
write-host "True"
$inuser.LastLogonTimeStamp = $arrUsers["$inuser.samAccountName"]
$inuser.LastLogonTimeStamp = $inuser.LastLogonTimeStamp.adddays(30)
If ((Get-Date) -gt $inuser.LastLogonTimeStamp)
{
write-host $inuser.samAccountName "should be disabled"
}
Else
{
write-host $inuser.samAccountName "is still active"
}
}
}
Else
{
write-host "False"
}
I have 2 problems here.
First the "If ($arrUsers.ContainsKey("$inuser.samAccountName"))" doesn't seems working. I always get a false result.
Second, to replace the LastLogonTimeStamp using "$inuser.LastLogonTimeStamp = $arrUsers["$inuser.samAccountName"]", my LastLogonTimeStamp become blank.
Could someone able to provide some assistants?
You're not using variable expansion correctly. Object properties aren't expanded, so this
"$inuser.samaccountname"
is actually:
$inuser.ToString() + ".samaccountname"
To expand an expression in a string, you must surround it with $(), e.g.
"$($inuser.samaccountname)"
In your case, however, you don't even need to do that. Leave the quotes out entirely:
$arrusers[$DN]
$arrusers.ContainsKey($inuser.samaccountname)
See the about_Quoting_Rules help topic for details.
I have solved this by assign the samAccountName value to another variable:
$tmpAccountName = $inuser.samAccountName
then
If ($arrUsers.ContainsKey("$tmpAccountName"))
instead of throw the $inuser.samAccountName directly to the checking. Not so sure why it cannot be read directly however at least it is solved now =). Same goes to the problem #2.