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

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.

Related

Powershell Active Directory Get Expired Users

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.

How to add items to an array in a function as a script variable on Powershell?

I am trying to add items to an array variable that I am declaring outside of a function.
Here is the idea of my code in a very simplified way:
function Test($NAME, $SPEED){
$fName = "testName"
$fSpeed = 100
if($status = ($fName -eq $NAME) -and ($fSpeed -eq $SPEED))
{}
else{
if($fName -ne $NAME)
{$errorMessages += "The name is not" + $NAME}
if($fSpeed -ne $SPEED)
{$errorMessages += "The speed is not" + $SPEED}
}
return $status
}
$script:errorMessages=#()
$result=#()
$result += Test -NAME "alice" -SPEED "100"
$result += Test -NAME "bob" -SPEED "90"
#result is an array of booleans that I need later on
$errorMessages
When I display $errorMessages, this is the expected output that I'd like:
The name is not alice
The name is not bob
The speed is not 90
However, when I try to display the variable outside of the function, and even outside of the "else" block, I get nothing printed out. How can I correctly add the error messages to the array?
You want to call errorMessages via the script scope. Therefore you've to use $script:errorMessage (instead of $errorMessage) inside your function.
function Test($NAME, $SPEED) {
$fName = "testName"
$fSpeed = 100
$status = ($fName -eq $NAME) -and ($fSpeed -eq $SPEED)
if (!$status) {
if ($fName -ne $NAME) {
$script:errorMessages += "The name is not" + $NAME
}
if ($fSpeed -ne $SPEED) {
$script:errorMessages += "The speed is not" + $SPEED
}
}
$status
}
$errorMessages = #()
$result = #()
$result += Test -NAME "alice" -SPEED "100"
$result += Test -NAME "bob" -SPEED "90"
#result is an array of booleans that I need later on
$errorMessages
Now you get the expected output:
The name is notalice
The name is notbob
The speed is not90
Also be aware about the return statement in PowerShell -> stackoverflow answer
Hope that helps

Powershell scripting for url custom monitoring

I am trying to build a custom script for URL monitoring. I am able to run the URL's from the file and enter the same in a logfile(named with time stamp).
Till here I have completed
Issue is when I compare the values from present(present timestamp) and previous logfile(previous timestamp).
This portion is not working fine. Please help me correct it.
Here is my code trying to compare value line by line from present logfile and previous logfile and run commands to generate output:
# New log is new logfile data
$Newlog = Get-Content $URLlogfile
$old_file = Dir C:\Scripts\logs | Sort CreationTime -Descending | Select Name -last 1
# Old log is Old logfile data
$oldlog = Get-Content $old_file -ErrorAction SilentlyContinue
Foreach($logdata in $Newlog) {
$url = ($logdata.Split(" "))[0]
$nodename = ($logdata.Split(" "))[1]
$statuscheck = ($logdata.Split(" "))[2]
$description = ($logdata.Split(" "))[3]
$statuscode = ($logdata.Split(" "))[4]
Foreach($log1data in $oldlog) {
$url1 = ($log1data.Split(" "))[0]
$nodename1 = ($log1data.Split(" "))[1]
$statuscheck1 = ($log1data.Split(" "))[2]
$description1 = ($log1data.Split(" "))[3]
$statuscode1 = ($log1data.Split(" "))[4]
While ($url = $url1) {
if ($statuscheck = $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck = Fail) {
While ($statuscheck1 = Pass) {
write-output "$url is down at $nodename1- testing event sent"
}
} elseif ($statuscheck = Pass) {
While ($statuscheck1 = Fail) {
write-output "$url is up at $nodename1- testing event sent"
}
}
}
Break
}
}
#At end am clearing the old logs except present one
dir C:\Scripts\logs -recurse | where { ((get-date)-$_.creationTime).minutes -gt 3 } | remove-item -force
Per the comment from BenH, the following part of your code needs correcting as follows:
If ($url -eq $url1) {
if ($statuscheck -eq $statuscheck1 ) {
write-output "output is same"
} elseif ($statuscheck -eq 'Fail' -and $statuscheck1 -eq 'Pass') {
write-output "$url is down at $nodename1- testing event sent"
} elseif ($statuscheck -eq 'Pass' -and $statuscheck1 -eq 'Fail') {
write-output "$url is up at $nodename1- testing event sent"
}
}
Corrections:
In your comparison statements the = needs to be -eq. In PowerShell = always assigns a value.
In your comparison statements Pass and Fail need to be surrounded by single quotes so they are treated as strings (otherwise they are treated like function statements, for functions which don't exist).
I've replaced the While statements with If statements. I'm not sure what the intent of those was but I think they'd just get stuck in an infinite loop as the variable they test is never changed from within the loop.

Boolean NoteProperty becomes an Array

The title says it all single boolean value becomes an array when assigned to a NoteProperty using Add-Member or using splatting.
PSVersion: 5.0.1xx
I have what I consider a strange problem. I am creating a PSObject with one of the NoteProperty members as a boolean. The function loops through a list, calls a function to perform an evaluation, creates an object and then adds it to an array. This seems to only happen to the first object created but I have not tested this with 5 or more objects being created.
I have validated that the functions are actually returning bool and that the variable being assigned to the property is an bool.
My workaround seems solid but am curious as to why this is happening.
Here's part of the code:
$clientCertsRequired = Get-Is-Client-Cert-Required -configFile $configFile -siteName $siteName
$httpsStatus = "Https is not enabled"
$clientCertStatus = "Client certs are not required"
if ($httpsEnabled -eq $true) {
$httpsStatus = "Https is enabled"
}
if ($clientCertsRequired -eq $true){
$clientCertStatus = "Client certs are required"
}
$sc = New-Object PSObject -Property #{
SiteName = $siteName;
ConfigFilePath = $path;
HttpsEnabled = $httpsStatus;
ClientCertStatus =$clientCertStatus;
ClientCertRequired = $clientCertsRequired;
}
# clean up of some inexplicable problem where assignment to property
# produces array with actual value in the last element.
if ($sc.ClientCertRequired.GetType().Name -eq "Object[]"){
$sc.ClientCertRequired = $sc.ClientCertRequired[-1]
}
$si += $sc
Function Get-Is-Client-Cert-Required{
param(
[xml]$configFile,
[string]$siteName
)
$functionName = $MyInvocation.MyCommand.Name
$clientCertRequired = $false
try{
# then read locations section (this will often not have any pages
$locationPath = "//configuration/location[#path='$siteName']"
[system.xml.xmlelement]$location = $configFile.DocumentElement.SelectSingleNode($locationPath)
if($location -ne $null){
[system.xml.xmlelement]$accessNode = $location.SelectSingleNode("system.webServer/security/access")
[system.xml.xmlelement]$authenticationNode = $location.SelectSingleNode("system.webServer/security/authentication")
[system.xml.xmlelement]$clientCertMappingNode
[system.xml.xmlelement]$iisClientCertMappingNode
[int]$sslFlagMask = 0
if($accessNode -ne $null){
$sslFlags = $accessNode.Attributes.GetNamedItem("sslFlags")
# $sslFlags = $accessNode.Attributes["sslFlags"].Value
if($sslFlagMask -ne $null){
$sslFlagMask = Convert-Ssl-Flag-String-To-Int-Flag -sslFlag $sslFlags.Value
}
}
if($authenticationNode -ne $null){
[system.xml.xmlelement]$clientCertMappingNode = $authenticationNode.SelectSingleNode("clientCertificateMappingAuthentication[#enabled='true']")
[system.xml.xmlelement]$iisClientCertMappingNode = $authenticationNode.SelectSingleNode("iisClientCertificateMappingAuthentication[#enabled='true']")
}
$clientCertAccepted = ($sslFlagMask -band $certAccepted) -eq $certAccepted
$clientCertRequired = Check-IIS-Express-SSL-Config $sslFlagMask
if($clientCertRequired -eq $false){
if($clientCertAccepted -and ($clientCertMappingNode -ne $null -or $iisClientCertMappingNode -ne $null)){
$clientCertRequired = $true
}
}
}
}catch{
$exceptionMessage = Get-Formatted-Exception-String -exceptionObject $_
$message = "$functionName - Exception`: $exceptionMessage"
Add-Exception -exception $message
Log-Error -message $message
}
$clientCertRequired
}
In the body of the Get-Is-Client-Cert-Required function, you do:
[system.xml.xmlelement]$clientCertMappingNode
[system.xml.xmlelement]$iisClientCertMappingNode
This pattern:
[type]$nonExistingVariable
Is a terrible idea in PowerShell - unlike C#, PowerShell does not have the concept of bare variable declarations, and the above pattern simply casts $null to the specified type, emitting a new instance of said type if it succeeds - this is likely what causes the function to output an array.
If you really need to bind a variable to a specific type, cast on assignment:
[type]$Variable = Get-Stuff
Bonus tip: The PowerShell-idiomatic naming convention for functions and cmdlets is Noun-Verb, with only a single hyphen. A more appropriate name for the function would be:
Test-ClientCertRequirement

Powershell foreach loop with multiple if statements

I have a script snippet that basically gets some unformated xml type output from a command.
Then in a defined filter section I'm transforming that into xml and run a search loop on each node, as from the part below.
What I'm trying to figure out is how I can make a multiple if -and loop, like
if (($CimProperty.VALUE -eq $somevariable) -and ($CimProperty.VALUE -eq $something-else))
The only problem is that since it's a foreach loop it won't take it, as it takes each property at a time and then the 'if statement -and portion' for it, which doesn't work since it's the same xml type property section.
In other words the loop doesn't go through the entire array to identify both conditions from the if statement.
PS code snippet:
filter Import-CimXml
{
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
foreach ($CimProperty in $CimXml.SelectNodes(“/INSTANCE/PROPERTY”))
{
if ($CimProperty.VALUE -eq $somevariable)
{
write-host "found it"
}
}
}
I hope the scenario is clear, thanks everyone in advance!
For just two conditions, you can make it fairly straight forward like (the untested);
filter Import-CimXml
{
$foundfirst = $false
$foundsecond = $false
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
foreach ($CimProperty in $CimXml.SelectNodes(“/INSTANCE/PROPERTY”))
{
if ($CimProperty.VALUE -eq $somevariable)
{
$foundfirst = $true
}
if ($CimProperty.VALUE -eq $someothervariable)
{
$foundsecond = $true
}
}
if ($foundfirst -and $foundsecond)
{
write-host "found it"
}
}
For more conditions, you may want to use arrays of corresponding matchwords/booleans instead.
Just extend your xpath query to do it all. It's less code and should be more efficient.
filter Import-CimXml
{
$CimXml = [Xml]$_
$CimObj = New-Object -TypeName System.Object
if($CimXml.SelectNodes(“/INSTANCE[PROPERTY='$somevariable' and PROPERTY='$someothervariable']”).Count -gt 0) {
write-host "found it"
}
}