Cannot index into a null array with two PSCustomObjects - powershell

My code builds two PSCustomObjects. Both objects can be $null, either Object can be $null. I test for that like this
$ADResult = #()
if ([string]::IsNullOrWhiteSpace($ADGroups)) {
Write-Warning "No AD Groups"
$ADResult = [PSCustomObject]#{
ADGroups = ""
ADGroupsdistinguishedName = ""
}
}
Else {
foreach ($group in $ADGroups) { do stuff }
The problem is when both objects are $null. When I put the objects together for a report. I get the error "Cannot index into a null array."
[int]$max = $ADResult.count
if ([int]$GResult.count -gt $max) { [int]$max = $GResult.count }
$Result = #()
for ( $i = 0; $i -lt $max; $i++) {
$Result += [PSCustomObject]#{
PrimaryEmail = $email
Title = $UserInfo.title
Department = $UserInfo.Department
Manager = $Manager
EmailBackup = $ENV:Backup
AccountDisabled = $ENV:ADDisabled
GoogleRemoved = $ENV:RemoveGoogle
ADGroupName = $ADResult.ADGroups[$i]
ADGroupNameDistinguishedName = $ADResult.ADGroupsdistinguishedName[$i]
GoogleGroup = $GResult.GoogleGroups[$i]
Role = $GResult.role[$i]
DateOfSeparation = (Get-Date).ToString("yyyy_MM_dd")
UnixID = $unix
UserDistinguishedName = $UserInfo.distinguishedName
UserOU = $UserInfo.Ou
PrimaryGroup = $UserInfo.primaryGroup.Split('=').Split(',')[1]
}
}
How can I overcome this better?
I want the other information like ou and related if both objects are $null

Change the value of the properties in your "empty" placeholder object from an empty string to a empty array:
$ADResult = [PSCustomObject]#{
ADGroups = #()
ADGroupsdistinguishedName = #()
}

Related

Find duplicates in array of hashtables

I have an array of hashtables and I need to find if there are elements who has the same Name.
I have this HasDuplicate function which return True or False if the array contains duplicate or not.
What I am doing here is that I am iterating through each element and add Name of it to another array, and then check it if it exists. But this code does not looks good, and I was thinking if there is another way of achieving this
# object looks like this
$array = #(
#{ Name = 'First', Passed = $True }
#{ Name = 'First', Passed = $False }
)
Function HasDuplicate
{
param($array)
$all = #()
foreach($item in $array)
{
$item_name = $item.Name
if($all -contains $item_name)
{
Write-Error "Duplicate name ""$item_name"""
return $True
}
else
{
$all += $item_name
}
}
return $False
}
Group-Object is probably the easiet, something like this:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$array.Name | Group-Object | Where-Object Count -GT 1
Another way you could do it using an hash table:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$h = #{}
$array | % {$h[$_.Name] += 1 }
$h.GetEnumerator() | Where value -GT 1
This might not be very good looking compared to the other answers, but you could just count your names in another hashtable then output the duplicates afterwards.
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
);
# Count names in array
$counts = #{}
foreach ($object in $array) {
$name = $object.Name
if (-not $counts.ContainsKey($name)) {
$counts[$name] = 0
}
$counts[$name] += 1
}
# Output duplicates
foreach ($name in $counts.Keys) {
if ($counts[$name] -gt 1) {
Write-Output ("Duplicate Name: " + $name)
}
}
Output:
Duplicate Name: First

Change SPWeb to a Particular Path Straight Away

I would like to change my PowerShell script to define a $spWeb value to a particular site straight away in a site collection, instead of going through the multiple foreach loops.
Is it possible?
Code sample:
$url = "http://testweb.com"
$siteCollection = Get-SPSite $url
$WebApp = $siteCollection.WebApplication
$url = $url+"/sites/site1"
foreach ($Site in $WebApp.Sites)
{
foreach($spWeb in $Site.AllWebs)
{
if ($spWeb.URL -eq $url)
{
//Skip previous foreach loops and declare $spWeb value with a fixed URL here
$users = $spWeb.SiteUserInfoList
$groups = $users.Items
$RList = $spWeb.Lists["Records"]
$RCount = 0
#GET Records
#$outputnewwebtext = "GET Records in $($spWeb.URL)"
#$outputnewwebtext |Tee-Object MRCE_Records.txt -Append
$query = New-Object Microsoft.SharePoint.SPQuery
$query.Query = '<Where><Eq><FieldRef Name="ContentType" /><Value Type="Computed">Item</Value></Eq></Where>'
$query.ViewAttributes = "Scope='RecursiveAll'";
$records = $Rlist.GetItems($query)
for ($intIndex =$records.Count - 1; $intIndex -gt -1; $intIndex--)
{
$RCount ++
}
$outputnewwebtext = "Records in $($spWeb.URL): $RCount`n"
}
}
}
Try Get-SPWeb command.
Get-SPWeb http://sp10/sites/team1

PowerShell Active Directory import script failing with PS 3.0 or above

I don't know much about PowerShell but have inherited a script from someone who is no longer available for assistance. This script imports AD Group Info and memberships related to Users and Computers. It works fine when run on a machine with PS 2.0 but it crashes if executed on PS 3.0 or newer.
I have not been able to figure out what needs to be modified but it seems the errors start occurring in the "Computer" membership import step and there are hundreds of errors that all say:
Command failed while processing computers: , Exception of type 'System.OutOfMemoryException' was thrown
Then at some point it looks like the script just stops and it never even gets to the 3rd step / function.
Any advice?
[Reflection.Assembly]::LoadWithPartialName("System.DirectoryServices") | Out-Null
$DBServer = "DBSERVER"
$DBName = "DBNAME"
$TableUsers = "[$DBName].[dbo].[AD_GroupToClient]"
$TableComps = "[$DBName].[dbo].[AD_GroupToDevice]"
$TableGroups = "[$DBName].[dbo].[AD_Group_Info]"
$sqldateformat = "yyyy/MM/dd HH:mm:ss:fff"
[system.Data.SqlClient.SqlConnection]$global:SqlConnection = $null
function Get-ScriptPath { $Invocation = (Get-Variable MyInvocation -Scope 1).Value; Split-Path $Invocation.MyCommand.Path }
$ScriptPath = Get-ScriptPath
$Logfile = "$ScriptPath\OutLog.log"
function Write-Logfile {
param($logtext)
[string](Get-Date -format $sqldateformat) + "`t$logtext" | Out-File $Logfile -Encoding ascii -Append
}
function Open-Database {
$global:SqlConnection = New-Object system.Data.SqlClient.SqlConnection
try {
$global:SqlConnection.ConnectionString = "Server=$DBServer;Database=$DBName;Integrated Security=True"
$global:SqlConnection.Open() | Out-Null
Write-Logfile "OK`tDatabase opened"
} catch {
Write-Host "Error Opening SQL Database`t$($_.Exception.Message)"
Write-Logfile "Error`tDatabase open failed, $($_.exception.message)"
exit
}
}
function Close-Database {
$global:SqlConnection.Close()
Write-Logfile "OK`tDatabase closed"
}
function Esc-Quote {
param($str)
if ($str) { $str.Replace("'","''") }
}
function Run-DBCommand {
param($SqlCommands, [switch]$getnumrows)
if ($SqlCommands.Count -ge 1) {
$SqlCommandText = [string]::Join(";", $SqlCommands)
try {
$SqlCmd = New-Object Data.SqlClient.SqlCommand($SqlCommandText, $SqlConnection)
$returnvalue = $SqlCmd.ExecuteNonQuery()
if ($getnumrows) { return $returnvalue }
} catch {
Write-Logfile "Error`tSQL Command failed, $($_.exception.message)"
}
}
}
function Run-GroupMemberExport {
param($exportmode)
switch ($exportmode) {
"users" {
$dom = [ADSI]"LDAP://OU=Clients123,DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=user)(objectCategory=person)(samaccountname=*))"
$table = $TableUsers
$namecolumn = "AD_Group_Member_Name"
$attribs = #("samaccountname")
}
"computers" {
$dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=computer)(samaccountname=*))"
$table = $TableComps
$namecolumn = "AD_Group_Member_Device"
$attribs = #("samaccountname", "whencreated")
}
}
$starttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()
$count = 0
$numaccounts = $results.Count
foreach ($res in $results) {
try {
$objAccount = $res.GetDirectoryEntry()
$samaccountname = $objAccount.properties["samaccountname"][0]
$whencreated = ""
if ($exportmode -eq "computers") { $whencreated = Get-Date ([datetime]$objAccount.properties["whencreated"][0]) -Format $sqldateformat }
$count++
Write-Progress "Querying accounts" $samaccountname -PercentComplete ($count * 100.0 / $numaccounts)
$objAccount.psbase.RefreshCache("tokenGroups")
$SIDs = $objAccount.psbase.Properties.Item("tokenGroups")
$groups = #()
ForEach ($Value In $SIDs) {
$SID = New-Object System.Security.Principal.SecurityIdentifier $Value, 0
try {
$Group = $SID.Translate([System.Security.Principal.NTAccount]).Value
} catch {
$Group = $SID.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
if ($groups -notcontains $Group -and $Group.Split("\")[1] -ne $samaccountname) { $groups += $Group }
}
Run-DBCommand #("DELETE FROM $table WHERE [$namecolumn] = '$(Esc-Quote $samaccountname)'")
$sqlcommands = #()
$currenttime = (Get-Date).ToUniversalTime().ToString($sqldateformat)
if ($groups) {
$groups | sort | foreach {
if ($exportmode -eq "users") {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)', '$currenttime')"
} else {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $_)', '$currenttime', '$whencreated')"
}
if ($sqlcommands.count -ge 50) { Run-DBCommand $sqlcommands; $sqlcommands = #() }
}
} else {
if ($exportmode -eq "users") {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve groups', '$currenttime')"
} else {
$sqlcommands += "INSERT INTO $table ([$namecolumn], [AD_Group_Name], [Last_Update], [Record_Created]) VALUES ('$(Esc-Quote $samaccountname)', 'ERROR: Unable to retrieve groups', '$currenttime', '$whencreated')"
}
}
Run-DBCommand $sqlcommands
} catch {
Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
}
}
Write-Progress " " " " -Completed
if ($count -eq $numaccounts) {
$numdeleted = Run-DBCommand #("DELETE FROM $table WHERE [Last_Update] < '$starttime' OR [Last_Update] IS NULL") -getnumrows
Write-Logfile "OK`tUpdates for $exportmode completed, $numdeleted old records deleted."
}
}
function Run-GroupDescriptionExport {
$dom = [ADSI]"LDAP://DC=test1,DC=test2,DC=test3"
$query = "(&(objectClass=group)(samaccountname=*))"
$table = $TableGroups
$attribs = #("samaccountname", "displayname", "description", "whencreated", "managedby", "grouptype","distinguishedname","whenchanged")
$srch = New-Object DirectoryServices.DirectorySearcher($dom, $query, $attribs)
$srch.PageSize = 1000
$srch.Sort = New-Object DirectoryServices.SortOption("sAMAccountName", [DirectoryServices.SortDirection]::Ascending)
$results = $srch.FindAll()
$count = 0
$numgroups = $results.Count
$sqlcommands = #()
$starttime = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
foreach ($res in $results) {
$count++
$samaccountname = $res.properties["samaccountname"][0]
Write-Progress "Querying accounts, $count/$numgroups" $samaccountname -PercentComplete ($count * 100.0 / $numgroups)
$displayName = ""; if ($res.properties.contains("displayname")) { $displayName = $res.properties["displayname"][0] }
$description = ""; if ($res.properties.contains("description")) { $description = $res.properties["description"][0] }
$managedby = ""; if ($res.properties.contains("managedby")) { $managedby = $res.properties["managedby"][0] }
$grouptype = ""; if ($res.properties.contains("grouptype")) { $grouptype = $res.properties["grouptype"][0] }
$distinguishedname = ""; if ($res.properties.contains("distinguishedname")) { $distinguishedname = $res.properties["distinguishedname"][0] }
$whencreated = ""; if ($res.properties.contains("whencreated")) { $whencreated = ([datetime]$res.properties["whencreated"][0]).ToString($sqldateformat) }
$whenchanged = ""; if ($res.properties.contains("whenchanged")) { $whenchanged = ([datetime]$res.properties["whenchanged"][0]).ToString($sqldateformat) }
$lastupdated = [datetime]::Now.ToUniversalTime().ToString($sqldateformat)
$sqlcommand = "DELETE FROM $table WHERE [AD_Group_Name] = '$(Esc-Quote $samaccountname)'; "
$sqlcommand += "INSERT INTO $table ([AD_Group_Name], [AD_Group_DisplayName], [AD_Group_Description], [Last_Update], [Managed_By],[Distinguished_Name],[Group_Category],[Created_On], AD_Last_Modified]) VALUES ('$(Esc-Quote $samaccountname)', '$(Esc-Quote $displayName)', '$(Esc-Quote $description)', '$lastupdated', '$(Esc-Quote $managedby)', '$(Esc-Quote $distinguishedname)', '$grouptype', '$whencreated','$whenchanged')"
$sqlcommands += $sqlcommand
if ($sqlcommands.count -ge 100) { Run-DBCommand $sqlcommands; $sqlcommands = #()
}
}
Run-DBCommand $sqlcommands
if ($numgroups -eq $count) {
Run-DBCommand #("DELETE FROM $table WHERE [Last_Update] <= '$starttime'")
}
Write-Progress " " " " -Completed
}
Open-Database
Run-GroupMemberExport "users"
Run-GroupMemberExport "computers"
Run-GroupDescriptionExport
Close-Database
This doesn't have anything to do with the PowerShell version. You're just plain running out of memory. You're pulling in a lot of data, so you need to be more conscious of getting rid of that data when you're done with it.
There are a couple things you can do to clean up memory:
First, the documentation for DirectorySearcher.FindAll() says:
Due to implementation restrictions, the SearchResultCollection class cannot release all of its unmanaged resources when it is garbage collected. To prevent a memory leak, you must call the Dispose method when the SearchResultCollection object is no longer needed.
So whenever you do:
$results = $srch.FindAll()
Make sure you call $results.Dispose() when you're done with it (at the end of the function).
Second, when you loop through the results in your Run-GroupMemberExport function, you're calling $res.GetDirectoryEntry(). Usually you can just let the garbage collector clean up DirectoryEntry objects, but when you're creating so many in a loop like that, the GC doesn't have time to run. This has happened to me when I've run a loop over thousands of accounts.
To solve this, you can call Dispose() on the DirectoryEntry objects yourself. Since you already have a try/catch block there, I would suggest adding a finally block to make sure it happens even if an error is thrown:
try {
...
} catch {
Write-Logfile "Error`tCommand failed while processing $exportmode`: $($objAccount.name), $($_.exception.message)"
} finally {
$objAccount.Dispose()
}
Actually, you could probably just not use GetDirectoryEntry() at all. Just ask the DirectorySearcher to return the other attributes you need. But if you want to still use it, then make sure you call RefreshCache for every attribute you need (you can put them all in one call to RefreshCache). If you access the Properties collection and ask for a value that it does not already have in cache, then it will ask AD for every attribute with a value - that's a lot of unnecessary data.

Check CSV for object not matching?

Probably a really elementary question but preliminary searches didn't return much. I'm writing a script that takes $CSC (which is user input) compares it to a .csv object called "CSC" and then populates the relevant information based off that.
$CSC = "1100 4WWW" #Hardcoded value to test against #CSC are always <#### #ChChCh>
$roster = Import-Csv <my path>
Foreach ($row in $roster) {
If ($CSC -eq $row.CSC) {
$Department = $row.Department
$Division = $row.Division
$Street = $row.Street
$City = $row.City
$State = $row.State
$Zipcode = $row.Zipcode
$OfficePhone = $row.Phone
$Country = $row.Country
} Else { }
}
That's working fine but how would I check if a user input $CSC didn't match an in the list?
Making the else or making an elseif ($CSC -ne $row.CSC) obviously returns a value for every line except the matching one. I'm guessing I should use a nested if statement but being self taught I wasn't sure what the best way to do this is. Thanks.
I can think of a couple things you can do.
Option 1: Using the -match operator. [not recommended as it has some regex constraints but I will present it as an option anyway]
$CSC = "1100 4WWW" #Hardcoded value to test against #CSC are always <#### #ChChCh>
$roster = Import-Csv <my path>
if ($roster -match $CSC)
{
Foreach ($row in $roster) {
If ($CSC -eq $row.CSC) {
$Department = $row.Department
$Division = $row.Division
$Street = $row.Street
$City = $row.City
$State = $row.State
$Zipcode = $row.Zipcode
$OfficePhone = $row.Phone
$Country = $row.Country
} Else { }
}
}
else
{
#your 'not a match' code goes here
}
Option 2: Set a flag
$CSC = "1100 4WWW" #Hardcoded value to test against #CSC are always <#### #ChChCh>
$roster = Import-Csv <my path>
$flag = $false
Foreach ($row in $roster)
{
If ($CSC -eq $row.CSC)
{
$Department = $row.Department
$Division = $row.Division
$Street = $row.Street
$City = $row.City
$State = $row.State
$Zipcode = $row.Zipcode
$OfficePhone = $row.Phone
$Country = $row.Country
$flag = $true
}
Else { }
}
If (!$flag)
{
#your 'not a match' code goes here
}

Unable to trim/trimend "$"

I am trying to modify a PS script from online resource:
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=computer)"
$Searcher.PropertiesToLoad.Add("samAccountName") > $Null
$Searcher.PropertiesToLoad.Add("lastLogon") > $Null
# Create hash table of users and their last logon dates.
$arrComp = #{}
# Enumerate all Domain Controllers.
ForEach ($DC In $D.DomainControllers)
{
$Server = $DC.Name
$Searcher.SearchRoot = "LDAP://$Server/" + $Domain.distinguishedName
$Results = $Searcher.FindAll()
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 ($arrComp.ContainsKey("$DN"))
{
If ($LastLogon -gt $arrComp["$DN"])
{
$arrComp["$DN"] = $LastLogon
}
}
Else
{
$arrComp.Add("$DN", $LastLogon)
}
}
}
Script above give me the computername & its' last logon date, however the computernames are having "$" at the end. I would like to trim the "$" in order for me to use it remove the computer from AD later. However my script is not working.
$Compdollar = $arrComp.getEnumerator() | Select-Object Key | out-string
$AllComp = #()
Foreach ($inactD in $Compdollar) {
$AllComp += $inactD.Trim("$")
}
$Allcomp
The output is still computer name with "$", can anyone tells me why it wasn't trimmed?
Don't use double quotes with a $ as it is treated like a variable. Use single quotes instead.
$AllComp += $inactD.Trim('$')
Or use the backtick to escape the dollar sign.
$AllComp += $inactD.Trim("`$")