Optimize Active Directory Audit script - powershell

As part of my job i'm constantly auditing active directory for given properties of cross domain accounts.
I've constructed a powershell script to output information to a CSV based on properties given to the script. This is fine, the script works beautifully for a small list of people however i'm noticing that the script slows down considerably when i provide a big list of users to audit.
This is the script:
$inputfile = "C:\Powershell\input.txt"
$users = Get-Content $inputfile
$audit = Read-Host "Audit Name"
$csv = ".\output\Audit\$audit.csv"
$failed = #()
$serv = #("server1", "server2", "server3")
if((Test-Path $csv) -eq $true){Remove-Item $csv}
foreach($domain in $serv)
{
$count = $users.Count
for( $i=0; $i -le $count - 1; $i++ )
{
if (($users.Get($i)) -ne "")
{
try
{
Write-Host "Checking for $($users.get($i)) on" -NoNewline
switch($domain)
{ # with fancier text for which domain we're searching
"server1" {write-host "...Server1" -ForegroundColor Cyan -NoNewline; $domainCsv = "Server1"}
"server2" {Write-Host "...Server2" -ForegroundColor White -NoNewline; $domainCsv = "Server2"}
"server3" {Write-Host "...Server3" -ForegroundColor Magenta -NoNewline; $domainCsv = "Server3"}
}
$usr = Get-ADUser -Identity $users.get($i) -Properties $properties -Server $domain | ? { ($_.distinguishedname -notlike '*Suspended*')}
if ($usr -ne $null)
{
$usr = Get-ADUser -Identity $users.get($i) -Properties $properties -Server $domain | ? { ($_.distinguishedname -notlike '*Deletion*')}
if ($usr -ne $null)
{
Write-Host "...Found" -ForegroundColor Green
$userobj = New-Object PSObject
Add-Member -InputObject $userobj -MemberType NoteProperty -Name "User" -Value $($users.Get($i))
Add-Member -InputObject $userobj -MemberType NoteProperty -Name "Domain" -Value $domainCsv
foreach($prop in $properties) {$userobj | Add-Member -MemberType NoteProperty -Name $prop -Value "$($usr.$prop)"}
$userobj | Export-Csv $csv -Append -NoTypeInformation
}
else
{
Write-Host "...Pending Delete" -ForegroundColor Red
$failed += "$($users.Get($i)),Pending deletion on $domainCsv"
}
}
else
{
Write-Host "...Suspended" -ForegroundColor Red
$failed += "$($users.Get($i)),Suspended on $domainCsv"
}
}
catch [System.Exception]
{
Write-Host "...Not found" -ForegroundColor Red
$failed += "$($users.Get($i)),Could not find on $domainCsv"
} # </Try
} # </If user ""
} # </For users
} # </For Domains
Add-Content $csv ""
Add-Content $csv "Those who failed, (Not found or Suspended or Pending deletion)"
Add-Content $csv ""
Add-Content $csv "User,Domain"
foreach($fail in $failed) {Add-Content $csv $fail}
Write-Host " "
Write-Host " Audit saved to $csv" -ForegroundColor Green
What the script does
Gets input file full of users (one name per line) (mostly 20 or so lines but sometimes the input file has been over 200)
user1
user2
user3
user4
user5
goes through each domain
checks if they're in an OU for suspended accounts
checks if they're in an OU for accounts pending deletion
if not in either OU grabs the information and puts it into a PSObject for insertion into a CSV
after its done it lists the accounts it couldnt find or were in an OU that i dont need to worry about.
As i'm quite new to powershell i have no idea if theres a way i can condense parts of the code to be quicker, i have read a few pages on optimizing powershell but the only change i could see was to change
for( $i=0; $i -le $users.count - 1; $i++ )
to
$count = $users.count
for( $i=0; $i -le $count - 1; $i++ )
My question is: How can i improve my script to loop faster when given more users?

As far as I understand your script spent most of the time in Get-ADUser. You call it twice with the same parameters, using $usr you should call it just one time, your script execution time should bedevided by two.
Another thing, I can't find the defenition of $properties in your script, reducing this list can also reduce the network payload.
Test something like this.
$usr = Get-ADUser -Identity $users.get($i) -Properties $properties -Server $domain | ? { ($_.distinguishedname -notlike '*Suspended*')}
if ($usr.distinguishedname -notlike '*Suspended*')
{
if ($usr.distinguishedname -notlike '*Deletion*')
{

Related

Subexpression printing out same strings? Powershell

I have this code which deletes User Profiles off a remote machine. The removal of profiles work just fine but, the Aesthetic of doing so doesn't. What do i mean?
I'm passing the user display names to an index and making a selection out of it, and that works fine in regards to assigning the proper names to the appropriate Index Number its associated to in C:\users.
The next line of code is it grabbing the selections i made, and running through them displaying the same name i did for the index, and then it goes off to delete the CIM instance.
So my question is, why is it not passing the subexpression $userinfo1 that is already made and not putting it into the next block of code, for example, the following works as in grabbing the proper Display Name and assigning it to the proper Number:
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
but, the next block doesn't associate the display name (that was captured in $userinfo1) with the number i select and it just continues to display the first display name with the rest of the profiles its reiterating through:
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "}
Hopefully this makes sense, and if anyone can point me in the right direction id greatly appreciate it!
Heres the rest of the script, please feel free to use it as it does work for deleting the actual profile off the system and not just the files.
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &PFL-Delete } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - account not in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($Profile in $menu[$selection]){
Write-Host "Deleting user: $(,$userinfo1[$selection]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist. Delete manually!"} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""}
}
}
#CountPs $cn
12/31/2020 - EDIT:
Here is the finished result:
Function Delete-PFL{
#Deletes a profile properly off remote machine. WARNING: DOES NOT BACK UP DATA! Use at your own peril. Delprofile
$cn = Read-Host -Prompt "Enter Computer Name"
$ping = Test-Connection -ComputerName $cn -Count 1 -Quiet
If($ping -eq $false){ Write-Host "Computer seems to be offline, please check name spelling." -ForegroundColor DarkYellow; Write-Host ""; &Delete-PFL } else {
$menu = (get-childitem "\\$cn\c$\users" | sort LastWriteTime -Descending).Name
$userinfo1 = foreach ($user in $menu) {
Start-Sleep -Milliseconds 2
$userinfo = (net user $user /domain | Select-String "Full Name" -ErrorAction SilentlyContinue) -replace "Full Name ", "" 2>&1 | Out-String -Stream
if ($userinfo.Length -lt 4) {
"$user - NO DISPLAY NAME in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 2) {
"$user - ACCOUNT NOT in ADUC" # output
}
else {
if ($LASTEXITCODE -eq 0){
$userinfo # output
}
}
}
}
Write-Warning "Ensure user profiles are no longer active and/or, have profiles be backed-up!"
Write-Host "RESULTS:" -BackgroundColor Black -ForegroundColor White
for ($i=0; $i -lt $userinfo1.Count; $i++) {
Write-Host "$($i): $($userinfo1[$i])"
} #END LIST OF POSSIBLE NAMES
Write-Host ""
Write-Host "For multiple users, seperate using a SPACE(1 2 3)"
$selection = Read-Host "ENTER THE NUMBER of the user(s) or Q to quit"
$selection = $selection -split " "
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
ID:$Profile "
$del = Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile }
If($del -eq $null){Write-Warning "No CIM instance found on system, profile has been deleted but files persist."
Write-Host "Attempting to delete files, please wait. . ."
Remove-Item -Path "\\$cn\c$\users\$Profile" -Force -WhatIf
Write-Host ""
Start-Sleep -Seconds 2
Write-Host "Checking if Files are still there. . ."
$TestPath = Test-Path -Path "\\$cn\c$\users\$Profile"
If($TestPath -eq $false){ Write-Host "Profile Files have been deleted. `
Continuing. . . ." -ForegroundColor Green
}
} else{
Get-CimInstance -ComputerName $cn -Class Win32_UserProfile | Where-Object { $_.LocalPath.split('\')[-1] -eq $Profile } | Remove-CimInstance -WhatIf
Write-Host "user profile has been deleted" -ForegroundColor Red
Write-Host ""
}
}
}
#CountPs $cn
}
Remember to remove the -whatif parameter. Enjoy!
$selection is an array of indices, so in your foreach loop you must refer to the single index at hand, not to $selection as a whole, to get the desired display output.
The conceptually clearest approach is probably to iterate over the indices contained in $selection:
foreach($index in $selection) {
$Profile = $menu[$index]
Write-Host "Deleting user: $($userinfo1[$index]) `
EDIPI:$Profile "
# ...
}

PowerShell Exporting

Can someone point me in the right direction? Basically, I would like to export the results of my testpath to a csv. Below is what I am working with. I have read a couple Microsoft documents but they only seem to confuse me even more. Any feedback is appreciated.
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
Foreach ($Computer in $ComputerList)
{
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference= 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
}
$Output =New-Object -TypeName PSObject -Property #{
} | Select-Object
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Assuming you want a record per user per computer, there's two things you want to change structurally:
Create new objects in the inner foreach loop
Assign all the objects created to $Output:
$ComputerList = (Get-ADComputer -Filter *).name
$ComputerList
write-host "`n"
$Output = Foreach ($Computer in $ComputerList) {
$userfolders = get-childitem "\\$Computer\C$\users\"
foreach ($user in $userfolders) {
$ErrorActionPreference = 'silentlycontinue'
$path = $user.fullname
write-host $path
$t = test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default"
IF ($t -eq 'True') {write-host "Has it" -ForegroundColor yellow} ELSE {write-host "no"}
write-host "`n"
New-Object -TypeName PSObject -Property #{
# We still need a bit of magic here
}
}
}
$Output | C:\Users\"user"\Chrome.csv
write-output "Script finished. Please check output files"
Now we just need to decide on what properties to add to our output objects:
New-Object -TypeName PSObject -Property #{
# We definitely want to know which computer and user profile the results are for!
ComputerName = $Computer
ProfileName = $user.Name
# And finally we want the results of `Test-Path`
Result = $t
}
Here's another option. Though nowhere near as elegant as what Matthias gave you. ;-}
It's just a refactor, to narrow down your code and pass everything directly and output by default, without the need for all the, Write-* stuff and the like. PowerShell just grants a number of ways to accomplish a use case.
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\Google\Chrome\User Data\Default") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results on screen
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
104DB2FE-76B8-4 no
Localhost no
127.0.0.1 no
#>
Clear-Host
$null = New-Item -Path 'C:\Temp\Chrome.csv' -Force
$Status = $null
$env:COMPUTERNAME,'Localhost', '127.0.0.1' |
Foreach {
Get-ChildItem "\\$PSItem\C$\users\" |
foreach {
$ErrorActionPreference = 'silentlycontinue'
# Use variable squeezing to assign and output to the screen
($path = $PSItem.fullname)
If (test-path -Path "$path\AppData\Local\MicrosoftEdge") {$Status = 'Has it'}
Else {$Status = 'no'}
}
[PSCustomObject] #{
ComputerName = $PSItem
Status = $Status
} | Export-Csv -Path 'C:\Temp\Chrome.csv' -Append
}
'Script finished. Please check output files'
# Results
<#
\\104DB2FE-76B8-4\C$\users\ContainerAdministrator
\\104DB2FE-76B8-4\C$\users\ContainerUser
\\104DB2FE-76B8-4\C$\users\Public
\\104DB2FE-76B8-4\C$\users\WDAGUtilityAccount
\\Localhost\C$\users\ContainerAdministrator
\\Localhost\C$\users\ContainerUser
\\Localhost\C$\users\Public
\\Localhost\C$\users\WDAGUtilityAccount
\\127.0.0.1\C$\users\ContainerAdministrator
\\127.0.0.1\C$\users\ContainerUser
\\127.0.0.1\C$\users\Public
\\127.0.0.1\C$\users\WDAGUtilityAccount
Script finished. Please check output files
#>
Import-Csv -Path 'C:\Temp\Chrome.csv'
# Results
<#
ComputerName Status
------------ ------
104DB2FE-76B8-4 Has it
Localhost Has it
127.0.0.1 Has it
#>

Function within a Function - Powershell

OK I am going to try to explain this as best as I can. What started out as a simple script has turned into a huge mess and now I cannot figure out how to get it working. I have been coming here for answers for some time so maybe you guys can help.
What I am trying to do is a import a list of systems and check to see if they are online. If they are online they go in one list and if not they go in another.
foreach ($server in $servers) {
if (Test-Connection $server -Count 1 -ea 0 -Quiet) {
Write-Host "$server Is Up" -ForegroundColor Green
$server | out-file -Append $liveSystems -ErrorAction SilentlyContinue
} else {
Write-Host "$server Is Down" -ForegroundColor Red
$server | out-file -Append $inactive -ErrorAction SilentlyContinue
}
}
From there I check to see if the application I need installed is on the systems. That is where things start to go off-track. When I run the function to process the $liveSystems file all I get is the last line of the file (or the same system over and over) and not each system as it should be.
function Is-Installed( $program ) {
$x86 = ((Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
$x64 = ((Get-ChildItem "HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall") |
Where-Object { $_.GetValue( "DisplayName" ) -like "*$program*" } ).Length -gt 0;
}
$program
function process-file1 {
param($filename)
Get-Content $filename -PipelineVariable line | ForEach-Object {
Is-Installed -program "My_Service"
if (Is-Installed -eq "True") {
Write-Host "$server has agent installed" -ForegroundColor Green
$server | Out-File $installed -ErrorAction SilentlyContinue
}
else
{
Write-Host "$server does not have agent installed" -ForegroundColor Red
$server | Out-File -Append $notInstalled -ErrorAction SilentlyContinue
}
}
}
process-file1 -filename $liveSystems
Once I can get the systems to process through the list of installed and not installed I am trying to take the list of installed systems and check which ones have the service running and which ones do not.
$array = #()
foreach($i in (gc $installed)) {
$svc = Get-Service my_service -ComputerName $i -ea "0"
$obj = New-Object psobject -Property #{
Name = $svc.name
Status = $svc.status
Computer = $i
}
$array += $obj
}
$array | Select Computer,Name,Status | Export-Csv -Path $resultsFile -
NoTypeInformation
Last but not least I run through that list of running and not running and attempt to start the service on systems that are not running.
function process-CSVfile2 {
param($filename)
Import-Csv $filename |
ForEach-Object -PipelineVariable object {
if($_.Status -eq "Running") {
Write-Host "Your Service is currently Running on" $_.Computer
}
if($_.Status -eq "Stopped") {
$serviceName = 'my_service'
$service = Get-CimInstance Win32_Service -ComputerName $_.Computer -Filter "Name=$serviceName"
$service.Start()
$service.WaitForStatus("Started",'00:00:30')
Start-Sleep 10
}
}
}
Several of these blocks run separately but when put together they will not run. I can't seem to get past the second block where it just looks at the same line over and over.
In addition there is a piece I have been trying to get working that would install the application on systems that do not have the service installed but that is not working either but I will save that for a different time.
If anyone can help me with this I would really appreciate it. After 3 days of trying to get it running I am at my wits end.
I'd create objects and properties instead of files with computers online etc...
Something like:
$Computers=New-Object -TypeName System.Collections.ArrayList
$Servers = #(Get-Content -path c:\servers.txt)
$Servers = $Servers | ? {$_} | select-object -uniqe |ForEach-Object {$_.TrimEnd()}
$Servers|ForEach-Object {
$tempobj=New-Object -TypeName PSObject
$tempobj | Add-Member -type NoteProperty -name Name -value $_
$tempobj | Add-Member -type NoteProperty -name isOnline -value $FALSE
$tempobj | Add-Member -type NoteProperty -name Installed -value $FALSE
$tempobj | Add-Member -type NoteProperty -name serviceRunning -value $FALSE
[void]$Computers.Add($tempobj)
then You could work on array (no need for additional files)
$Computers|Where-Object {$_.isOnline -eq $TRUE}
etc

PSObject bug with specific parameter

I have a powershell script that does some simple auditing of group membership on remote servers. The output is as expected except in the case of one group.
There are two parameters two this script, an OU to check in AD and a Group name to check. The OU parameter returns a list of server names, the Group name is the group to return members on. This all works fine except in one case, Backup Operators.
param([parameter(mandatory=$true)][string]$region,[string]$group)
### Debug flag for viewing output when running the script.
$DEBUG = 1
$self = $myinvocation.mycommand.name
function cmdopts {
if ($DEBUG) {
write-host "$self running with options"
write-host "Region: $region"
write-host "Group: $group"
}
}
### Function to handle custom messages to the user.
function usageRegion {
# Removed for brevity
}
function usageGroups {
# Removed for brevity
}
### Cleanup from previous run of the script.
function cleanup {
# Removed for brevity
}
#### Function to load powershell modules at runtime
function loadmod {
param([string]$name)
if ( -not(get-module -name $name)) {
if (get-module -listavailable| where-object { $_.name -eq $name}) {
import-module -name $name
$true
} else {
$false
}
} else {
$true
}
}
### Main()
cmdopts
#### Validate commandline options
if ( "cnr","nwr","swr","ner","ser","emr","lar","apr" -notcontains $region ) {
usageRegion
exit
}
if ( "Administrators","Backup Operators","Event Log Readers","Hyper-V Administrators","Power Users",
"Print Operators","Remote Desktop Users" -notcontains $group) {
usageGroups
exit
} else {
### We are creating three files for each run, previous runs need to be cleaned up before we start.
cleanup
### The ActiveDirectory module is a dependency for this script, we use it to get a list of machine names from AD for the OU.
if ( loadmod -name "activedirectory" ) {
write-host "Loading ActiveDirectory powershell module..." -foregroundcolor green
} else {
write-host "Sorry, you do not have the ActiveDirectory powershell module installed." -foregroundcolor yellow
write-host "The script cannot contnue." -foregroundcolor yellow
exit
}
### Get the list of servers from AD for the OU specified by the user.
get-adcomputer -f * -searchbase "ou=$region,ou=servers,dc=domain,dc=com" | select name | out-file "c:\scripts\ps\$region.srvtmp.txt" -append
### We need to fix some format issues with the file before continuing
# Removed for brevity, cleans up the file output from get-adcomputer and sets variable $srvlist
$srvlist = gc "c:\scripts\ps\$region.srvlist.txt"
# Store for the return
$store = #()
# Fix the group string for the filename
$filestring = $group
$filestring = $filestring.replace(' ', '')
$filestring = $filestring.tolower()
foreach ( $srv in $srvlist ) {
if ( $srv -eq "bustedserver" ) {
# This box hangs and does not tear down WMI when it can't complete, timeout does not work
write-host "skipping $srv"
} else {
$response = test-connection $srv -count 1 -quiet
### This does not work super well, might have to try a custom function
if ($response -eq $false ) {
write-host "$srv was offline during test" -foregroundcolor darkmagenta
} else {
write-host "Checking $group on " -nonewline; write-host $srv -foregroundcolor cyan
$groupinfo = new-object PSObject
$members = gwmi -computer $srv -query "SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$srv',Name='$group'`""
$members = $members | sort-object -unique
$count = 0
if ($members -ne $null) {
add-member -inputobject $groupinfo -membertype noteproperty -name "Server" -value $srv
add-member -inputobject $groupinfo -membertype noteproperty -name "Group" -value $group
foreach ($member in $members) {
$count += 1
$data = $member.partcomponent -split "\,"
$domain = ($data[0] -split "=")[1]
$name = ($data[1] -split "=")[1]
$line = ("$domain\$name").replace("""","")
add-member -inputobject $groupinfo -membertype noteproperty -name "Member $count" -value $line
}
}
if ($DEBUG) {
write-host $groupinfo
}
$store += $groupinfo
}
}
}
}
#$store | export-csv -path "$HOME\desktop\$region-$filestring-audit.csv" -notype
$store
If I run this script against a group like Administrators, or Remote Desktop Users the output looks like the following.
Server: SERVER1
Group: Remote Desktop Users
Member1: GroupName1
Member2: GroupName2
Member3: GroupName3
If I run this script against the group Backup Operators, I only get the first group even if there are many. In the debug write-host statement, it will show all of the groups. When printing the store, it only shows the first one. Even if there are two or more, it will alsways print...
Server: SERVER1
Group: Backup Operators
Member1: GroupName1
Any ideas on why this is broken specifically for 'Backup Operators' and not others would be appreciated.

Exporting info from PS script to csv

This is a powershell/AD/Exchange question....
I'm running a script against a number of Users to check some of their attributes however I'm having trouble getting this to output to CSV. The script runs well and does exactly what I need it to do, the output on screen is fine, I'm just having trouble getting it to directly export to csv.
The input is a comma seperated txt file of usernames (eg "username1,username2,username3")
I've experimented with creating custom ps objects, adding to them and then exporting those but its not working....
Any suggestions gratefully received..
Thanks
George
$array = Get-Content $InputPath
#split the comma delimited string into an array
$arrayb = $array.Split(",");
foreach ($User in $arrayb)
{
#find group memebership
Write-Host "AD group membership for $User"
Get-QADMemberOf $User
#Get Mailbox Info
Write-Host "Mailbox info for $User"
Get-Mailbox $User | select ServerName, Database, EmailAddresses, PrimarySmtpAddress, WindowsEmailAddress
#get profile details
Write-Host "Home drive info for $User"
Get-QADUser $User| select HomeDirectory,HomeDrive
#add space between users
Write-Host ""
Write-Host "******************************************************"
}
Write-Host "End Script"
EDITED....
Methods I have tried for exproting (showing only the for loop/export code)
Method1
$AllData = #()
foreach ($User in $arrayb)
{
#set title for this user
#Write-host "Details for $User"
#find out their group memebership
Write-Host "AD group membership for $User"
$AdMemberOf = Get-QADMemberOf $User
Write-Host "ad completed"
Write-Host ""
Write-Host ""
#Get Mailbox Info
Write-Host "Mailbox info for $User"
$ExInfo = Get-Mailbox $User | select ServerName, Database, EmailAddresses, PrimarySmtpAddress, WindowsEmailAddress
Write-Host "ex completed"
Write-Host ""
Write-Host ""
#get profile details
Write-Host "Home drive info for $User"
$HomeInfo = Get-QADUser $User| select HomeDirectory,HomeDrive
Write-Host "home drive completed"
#add space between users
Write-Host ""
Write-Host "******************************************************"
$ReturnedObj = New-Object PSObject
$ReturnedObj | Add-Member NoteProperty -Name "AD Group Membership for $User" -Value $AdMemberOf
$ReturnedObj | Add-Member NoteProperty -Name "Exchange details for $User" -Value $ExInfo
$ReturnedObj | Add-Member NoteProperty -Name "Home drive info for $User" -Value $HomeInfo
Write-Host $ReturnedObj
$AllData += $ReturnedObj
}
Write-Host "starting csv export"
Write-Output $AllData |export-csv -Path $OutputPath -notype -force
Write-Host "End Script"
Method2
$ExportInfo = #()
foreach ($User in $arrayb)
{
$UserInfo = New-Object System.Object
#set title for this user
$ExportInfo += "Details for $User"
#Get Mailbox Info
$ExportInfo += Get-Mailbox $User
#find out their group memebership
$ExportInfo += Get-QADMemberOf $User
#get profile details
$ExportInfo += Get-QADUser $User| select HomeDirectory,HomeDrive
#add space between users
$ExportInfo += ""
$ExportInfo += ""
}
$ExportInfo | Export-Csv -Path $OutputPath ;
EDITED:
Thanks to suggestion from Stej I now have the code below... Still doesn't work correctly. I have added a check to confirm the user exists in AD as well. First problem is with getting a users AD group membership - if I put a break point in and look at the value of a a specific users AD membership, the value in teh varialbe is error "Cannot process argument because the value of argument "obj" is null. Change the value of argument "obj" to a non-null value." No idea what's causing this, so I've just ignored it for now and commented out those lines for AD membership to concentrate on gettng the rest working.
With the lines commented out, the script runs fine and again, with a breakpoint at the bottom I can look at the value of $ExportInfo and they all look fine, ie they have all been saved into the variable correctly. However, it won't output. As you can see, I put a command to get $ExportInfo written to screen but that doesn't show anything. When it attempts to execute the Export line (ie "Export-Csv $ExportInfo -Path $OutputPath") it errors with:
"Cannot convert 'System.Object[]' to the type 'System.Char' required by parameter 'Delimiter'. Specified method is not supported.
At :line:80 char:10
+ Export-Csv <<<< $ExportInfo -Path $OutputPath"
I chnaged the line to "$ExportInfo | Export-Csv -Path $OutputPath" and it now exports to CSV... No idea why??? Two issues though... as noted above, AD groups aren't working and the field Email Addresses (which should return something like { SMTP:j.smith#domain.com, smtp: j.smith#domain.com.au, smtp: smithj#exchange.domain.com.au and SIP:j.smith#domain.com.au}) just shows up in the CSV as "Microsoft.Exchange.Data.ProxyAddressCollection". Again, checking in $ExportInfo, the addresses are there....
Thanks
$ExportInfo = #()
foreach ($User in $arrayb)
{
$CheckUser = Get-QADUser -Name $User
if (!$CheckUser)
{
$CountUser++
Write-Warning "############################################"
Write-Warning "$user not found in AD"
Write-Warning "############################################"
}
else
{
$CountUser++
$UserInfo = New-Object System.Object
#find out their group memebership
Write-Host "AD group membership for $User"
#$Temp = Get-QADMemberOf $User
#$UserInfo | Add-Member NoteProperty -Name "AD Group Membership" -Value $Temp.Name
#set title for this user
#Write-host "Details for $User"
#Get Mailbox Info
Write-Host "Mailbox info for $User"
$Temp = Get-Mailbox $User #| select ServerName, Database, EmailAddresses, PrimarySmtpAddress, WindowsEmailAddress
$UserInfo | Add-Member NoteProperty -Name "ServerName" -Value $Temp.ServerName
$UserInfo | Add-Member NoteProperty -Name "Database" -Value $Temp.Database
$UserInfo | Add-Member NoteProperty -Name "Email Addresses" -Value $Temp.EmailAddresses
$UserInfo | Add-Member NoteProperty -Name "Primary SMTP" -Value $Temp.PrimarySmtpAddress
$UserInfo | Add-Member NoteProperty -Name "Windows Email Address" -Value $Temp.WindowsEmailAddress
#$ReturnedObj | Add-Member NoteProperty -Name
#get profile details
Write-Host "Home drive info for $User"
$Temp = Get-QADUser $User #| select HomeDirectory,HomeDrive
$UserInfo | Add-Member NoteProperty -Name "Home Directory Location" -Value $Temp.HomeDirectory
$UserInfo | Add-Member NoteProperty -Name "Home Drive Mapped To" -Value $Temp.HomeDrive
#add space between users
Write-Host ""
Write-Host "******************************************************"
$ExportInfo += $UserInfo
}#end else
}
Write-Host "blah"
Write-Host $ExportInfo
Export-Csv $ExportInfo -Path - $OutputPath
Write-Host "Number of Users processed: $CountUser"
Is there any error message? Is there something in $error[0]? What does it mean that it is not working?
Edited:
In your second method you create $UserInfo object, but you don't use it. I guess it was intended to be added to $ExportInfo. Instead of this you add bare strings to $ExportInfo and that's why you gave such a strange content of your csv file.
How it should be done correctly:
$UserInfo = New-Object System.Object
$ReturnedObj | Add-Member NoteProperty -Name "User Name" -Value $user.Name
$ReturnedObj | Add-Member NoteProperty -Name "User otherValue" -Value $user.othValue
#Get Mailbox Info
$mailBox = Get-Mailbox $User
$ReturnedObj | Add-Member NoteProperty -Name "User Mail box size" -Value $mailBox.Size
$ReturnedObj | Add-Member NoteProperty -Name "User Mail box -count of messages" -Value $user.countOfMessages
... # and so on
$ExportInfo += $ReturnedObj
Note that the objects that you want to export to csv have to have properties of type strings, integers, bools etc. Not composed objects like $User or the ones returned by Get-Mailbox $User. The values in csv have to be primitive. That's the reason.
That applies for your first example where you add NoteProperty with value $AdMemberOf. That is object itself that can not be exported to csv. You have to create bunch of properties for every interesting property of $AdMemberOf.
Turned out I needed to use some foreach loops to get the info I needed.... So:
foreach($user in $array)
{
$temp = Get-QADUser -Name $User
if (!$temp)
{
$log += "????????????????" + "`n"
$log += "$user not found in AD" + "`n"
$log += "????????????????" + "`n"
}
else
{
#find out their group memebership
$log += "AD group membership for $User" + "`n"
$temp = Get-QADMemberOf $User
foreach ($drive in $temp)
{
$temp2 = $drive
$temp2 = $temp2.Substring(6)
$log += "`t" + $drive + "`n"
}#end foreach for drive loop
$temp = Get-Mailbox $User
if ($temp.RecipientType -like "UserMailbox")
{
$log += "Mailbox info for $User" + "`n"
#$log += Get-Mailbox $User | select ServerName, Database, EmailAddresses, PrimarySmtpAddress, WindowsEmailAddress
$log += "Email Server: " + $temp.ServerName + "`n"
$log += "Email Database: " + $temp.Database + "`n"
$log += "Primary SMTP: " + $temp.PrimarySmtpAddress + "`n"
$log += "Windows Email Address: "+ $temp.WindowsEmailAddress + "`n"
#$log += "`n"
foreach ($e in $temp.EmailAddresses)
{
$log += "`t" + "Email Addresses: " + $e + "`n"
}
}
else
{
$log += "########" + "`n"
$log += "$User is not a MailboxUser, IE no Exchange Mailbox" + "`n"
$log += "########" + "`n"
}
$log += "Home drive info for $User" + "`n"
$temp = Get-QADUser $User| select HomeDirectory,HomeDrive
$log += "Home Directory: " + $temp.HomeDirectory + "`n"
$log += "Home Drive Letter: " + $temp.HomeDrive + "`n"
$tempvar = [string] $temp.HomeDirectory
if ($tempvar -eq "")
{
$noHomedirectory += $User + "`n"
$countNoHOmeDirectory ++
}
}#end of the main if/else to determine if the AD account exists
$OutputPath = "C:\SomeFolder\"+"User_Report_"+([datetime]::Now).tostring("yyyyMMddhhmmss")+".txt"
$log | Out-File -FilePath $OutputPath
I then dump all the specifc logs (eg $noHomeDirectory etc) in the body of an email, attach the complete log as exported above and send it to myself.
Thanks for everyone's suggestions above and sorry for the delay in posting back the answer...