Foreach loop and writing the results to a file - powershell

I'm working on a script which iterates through all users found across a domain, grabs a few credentials and then returns them in the format of an SQL INSERT statement which I want stored in a .txt file as output.
So far I've only been able to write the last user to a file however I'm able to print out in the terminal every single user. I have a feeling that I'm overwriting the .txt output file each time I iterate through my foreach loop.
Below is my code which has been sanitised:
$users = Get-ADUser -Properties uidNumber, sAMAccountName -SearchBase' OU=LiveUsers,OU=Users,OU=MyBusiness,DC=local' -Filter *
$message = ""
Set-Content -Path C:\Desktop\UIDs\currentList.txt -Value $null # ensures file is blank
foreach ($user in $users | Select-Object -Property uidNumber, sAMAccountName){
#Search in specified OU and List above for UID and name and write to a file
$message = "INSERT INTO `DataBaseNameHere`.`currentUser` (`User_id`, `User_name`) VALUES ('" + $user.uidNumber + "', '" + $user.sAMAccountName + "');" |
Out-File -FilePath C:\Desktop\UIDs\currentList.txt
}
Get-Content -Path C:\Desktop\UIDs\currentList.txt
I've tried other variations of foreach loops, Out-File and Tee-Object so far.

Assuming that the sanitized code you provided does what you want except for leaving only a single line in the output file, you need to ensure that you have either no existing output file or that it's blank, and then you add the -append switch to the Out-File cmdlet:
$users = Get-ADUser -Properties uidNumber, sAMAccountName -SearchBase 'OU=LiveUsers,OU=Users,OU=MyBusiness,DC=local' -Filter *
Set-Content -Path C:\Desktop\UIDs\CurrentList.txt -Value $null # ensures file is blank
foreach ($user in $users | Select-Object -Property uidNumber, sAMAccountName) {
$message = "INSERT INTO `databaseNameHere`.`currentUser` (`User_id`, `User_name`) VALUES ('" + $user.uidNumber + "', '" + $user.sAMAccountName + "');"
Out-File -FilePath C:\Desktop\UIDs\currentList.txt -append # -append added to not overwrite existing content.
}
See Out-File at Microsoft Docs.

The code you posted would not write anything to a file since the loop defines $message without doing anything with it, and then calls Out-File without any input.
Something like this should do what you want:
Get-ADUser ... |
Select-Object uidNumber, sAMAccountName |
ForEach-Object { "INSERT INTO `databaseNameHere`.`currentUser` (...);" } |
Out-File -FilePath C:\Desktop\UIDs\currentList.txt
Beware though, that building INSERT statements that way is vulnerable to SQL injection and should be avoided.

So, after taking elements from a few of your answers I was able to modify my code and fix the loop. I was just using the Out-File cmdlet wrong.
$users = Get-ADUser -Properties uidNumber, sAMAccountName -SearchBase 'OU=LiveUsers,OU=Users,OU=MyBusiness,DC=myCompany,DC=local' -filter *
Clear-Content -Path C:\Desktop\UIDs\CurrentList.txt
foreach ($user in $users ){
“INSERT INTO `databasename`.`currentUser` (`User_id`, `User_name`) VALUES ('"+ $user.uidNumber + "','"+ $user.sAMAccountName +"');" |
Out-File -FilePath C:\Desktop\UIDs\CurrentList.txt -Append
}

Related

Trouble running loop with variable

I am writing a script to pull a list of users and then archive that list of users mailboxes and archives into psts
$Lines = Get-ADUser -Filter * -Properties EmailAddress
$Lines | select EmailAddress | Where {$_.EmailAddress -ne $null} | Out-File "C:\T2\EMailAddresses.txt"
(Get-Content "C:\T2\EMailAddresses.txt") | ForEach { $_.Trim() } | Set-Content "C:\T2\EMailAddresses.txt"
$Names = Get-Content "C:\T2\EMailAddresses.txt"
$Emails = $Names.Split("#")[0]
Add-PSSnapIn -Name Microsoft.Exchange, Microsoft.Windows.AD
$SubPath1 = "\\qnap1\archive\exchange_Migration\"
$SubPath2 = "_Primary_mailbox.pst"
$SubPath3 = "_Archive_mailbox.pst"
ForEach ($Email in $Emails) { New-MailboxExportRequest -Mailbox $Email -FilePath "$Subpath1 + $Email.Split("#")[0] + $SubPath2" ; New-MailboxExportRequest -Mailbox $Email.Split("#")[0] -FilePath "$Subpath1 + $Email + $SubPath3" -IsArchive }
Where I am strugglng is getting the variables correct. I want to use the full e-mail address with the export request command but only the name part of the email (not domain) in the path for the export
I think there's a few things going on. When you are unrolling the .Split("#") on the $Names array you are then indexing into it with [0]. That's going to return the first element of the array that resulted from the split. NOT the first element resulting from each split...
But there's not a need for that anyhow. You could simply a few ways, one example:
Add-PSSnapIn -Name Microsoft.Exchange, Microsoft.Windows.AD
$Lines = Get-ADUser -Filter "EmailAddress -like '*'" -Properties EmailAddress
$Lines |
Select-Object -ExpandProperty EmailAddress |
Out-File "C:\T2\EMailAddresses.txt"
(Get-Content "C:\T2\EMailAddresses.txt").Trim() |
Set-Content "C:\T2\EMailAddresses.txt"
$Emails = Get-Content "C:\T2\EMailAddresses.txt"
$SubPath1 = "\\qnap1\archive\exchange_Migration\"
$SubPath2 = "_Primary_mailbox.pst"
$SubPath3 = "_Archive_mailbox.pst"
ForEach ($Email in $Emails)
{
$Alias = $Email.Split("#")[0]
$FilePathPrimary = "$Subpath1" + $Alias + $SubPath2
$FilePathArchive = "$Subpath1" + $Alias + $SubPath3
New-MailboxExportRequest -Mailbox $Email -FilePath $FilePathPrimary
New-MailboxExportRequest -Mailbox $Email -FilePath $FilePathArchive -IsArchive
}
Note: I obviously can't test this.
You can use emails directly, then simply calculate the arguments for your export commands on each loop iteration. This comes out cleaner and easier to read.
Also Notice:
Use of Select-Object -ExpandProperty this will return just the email addresses for the file.
Unrolling the .Trim() method on the results of Get-Content instead of running it through a ForEach-Object loop.
Moving filter criteria left in the command/pipeline by leveraging the -Filter parameter of Get-ADUser, which is a performance best practice. Especially when commands have their own and/or adequate filtering capabilities to do so.
An Aside:
There are a few other things that can be done to simplify. However, the way this is presented thus far, there's actually no need to intermediate the data with files. So I'm going to skip making enhancements in that area. Instead you could take the output from Get-ADUser and use it directly. Again, lot's of ways to do it, but building off what we already have, here's one example:
Add-PSSnapIn -Name Microsoft.Exchange, Microsoft.Windows.AD
$SubPath1 = "\\qnap1\archive\exchange_Migration\"
$SubPath2 = "_Primary_mailbox.pst"
$SubPath3 = "_Archive_mailbox.pst"
$Emails = (Get-ADUser -Filter "EmailAddress -like '*'" -Properties EmailAddress).EMailAddresses
ForEach($Email in $Emails)
{
$Alias = $Email.Split("#")[0]
$FilePathPrimary = "$Subpath1" + $Alias + $SubPath2
$FilePathArchive = "$Subpath1" + $Alias + $SubPath3
New-MailboxExportRequest -Mailbox $Email -FilePath $FilePathPrimary
New-MailboxExportRequest -Mailbox $Email -FilePath $FilePathArchive -IsArchive
}
Warning:
Beyond the scope of the question, but all the code so far assumes the mailbox indeed has an enabled archive. If or when that is not the case the attempt to export the archive will likely throw up a bloody red error. You can handle that by introducing some error handling. Or, you might consider rewriting by using Get-Mailbox from the Exchange tools instead of Get-ADUser

Export data based on foreach login [Powershell]

I have simple csv file with column 'logins'
logins
john
mark
maria
...
Have powershell script to check their last logontime:
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName)
{
$time = 0
$user = Get-ADUser $userName | Get-ADObject -Properties lastLogon
if($user.LastLogon -gt $time)
{
$time = $user.LastLogon
}
$dt = [DateTime]::FromFileTime($time)
Write-Host $username $dt }
import-csv -Encoding UTF8 -path C:\scripts\loginy.csv | foreach {
Get-ADUserLastLogon -UserName $_.logins
}
This works fine with output
john 2018-05-10 14:11:28
mark 2018-11-29 14:26:58
maria 2018-11-02 11:14:17
...
When I try to export results it to csv file by code
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
$results = #()
foreach ($_.logins in $users) {
$results += Get-ADUserLastLogon -UserName $_.logins
}
$results | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
getting error
At C:\scripts\OstatnieLogowanie.ps1:19 char:12
+ foreach ($_.logins in $users) {
+ ~
Missing 'in' after variable in foreach loop.
At C:\scripts\OstatnieLogowanie.ps1:19 char:29
+ foreach ($_.logins in $users)
}
I can't get it work over 2 hours :/
Edit: I've confused LastLogon and LastLogonTimestamp. LastLogonDate is based on LastLogonTimestamp. The differences between these properties are explained here and here. I will come back and update my answer.
You're using Write-Host to output data:
Write-Host $username $dt
This won't work. Write-Host means "write to the console screen, not to standard output." That will work just fine if you're trying to display data, but calling $x = Get-ADUserLastLogon -UserName $login will print the results to the console screen and nothing would be assigned to the $x variable. For example:
PS C:\> $x = Write-Host 0
0
PS C:\> $x
PS C:\>
See how Write-Host still wrote to the console and $x doesn't have a value?
Your function should look something like $username, $dt or Write-Output $username, $dt or return $username, $dt.
Although that's still not really going to work like you want. I would probably use a custom object (see Get-Help about_Object_Creation -ShowWindow) like this:
Import-Module ActiveDirectory
function Get-ADUserLastLogon([string]$userName) {
$user = Get-ADUser $userName -Properties LastLogonDate
[PSCustomObject]#{'Logins' = $username; 'LastLogonDate' = $user.LastLogonDate}
}
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
$results = foreach ($user in $users) {
Get-ADUserLastLogon -UserName $user.logins
}
$results | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
Frankly, however, if I were doing what you're trying to do here, my actual code would look like this:
Import-Csv -Encoding -Path C:\scripts\loginy.csv |
Select-Object -ExpandProperty logins |
Get-ADUser -Properties LastLogonDate |
Select-Object #{n = 'Logins'; e = {$_.SamAccountName}}, LastLogonDate |
Export-Csv -Path C:\scripts\Eksporty\logowania.csv -Encoding UTF8 -NoTypeInformation
Select-Object -ExpandProperty logins will pass just the bare value of the logins column. Get-ADUser accepts identities from the pipeline, and it fetches the LastLogonDate for each user, as long as the SamAccountName (a default property) which is the logon name.
The next line, Select-Object #{n = 'Logins'; e = {$_.SamAccountName}}, LastLogonDate uses a calculated property (See the examples in Get-Help Select-Object -ShowWindow) to rename the SamAccountName property in a column named Logins. You could use Select-Object SamAccountName, LastLogonDate if you don't care about the column name. And the -NoTypeInformation parameter on Export-Csv just keeps it from adding that annoying "#TYPE System.Management.Automation.PSCustomObject" nonsense on the first line.
$_ is the variable for the current value in pipeline. In your second part of code, since you don't have a pipeline, hence $_ is empty and doesn't have any property/method associated with it.
What you can do is -
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
foreach ($user in $users) {
Get-ADUserLastLogon -UserName $user.logins | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
}
OR
$users = import-csv -Encoding UTF8 -path C:\scripts\loginy.csv
foreach ($_ in $users) {
Get-ADUserLastLogon -UserName $_.logins | Export-CSV C:\scripts\Eksporty\logowania.csv -Append -encoding "utf8"
}
Although I would recommend not using the latter since $_ is an automatic variable $PSItem and beside you can have plenty other names for a variable which are not keywords, functions etc.
The use of += to extend an array requires creating a new instance behind the scenes in every iteration.

Work with ADComputer output in foreach loop

I want to output all hostnames within a network first with a foreach loop, in order (for example) to be able to ping them.
However with the following code I do not get any output in the console. The CSV file will be saved, but what is written in the loop will not be executed.
Does anyone know what the reason for this is and how I can solve it?
Import-Module activedirectory
Get-ADComputer -Filter * -Property * | Select Name | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8 | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
}
This occurs because Export-CSV does not output an object. Sometimes cmdlets like this have a -PassThru parameter which you can use to have an object passed along, but thats not the case with Export-CSV, they simply expect it to always be the last cmdlet in the pipeline.
You should instead do this:
$Computers = Get-ADComputer -Filter * -Property * | Select Name
$Computers | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8
$Computers | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
}
You could also do this:
Get-ADComputer -Filter * -Property * | Select Name | ForEach {
$computerName = $_.Name
Write-Host $computerName
Write-Host "----"
$_
} | Export-CSV -Path $env:TEMP\ZZZEXPORTE.csv -NoTypeInformation -Encoding UTF8
Noting that we have to add $_ to our ForEach-Object loop so that it outputs the current item to the pipeline, but that our Write-Host statements don't effect the pipeline because they are writing to the console only. To be honest though, this is a bit harder to follow for anyone else reading your code.

PowerShell for-each loop output to file

How do I output/append the contents of this foreach loop to a text file?
The following below is not working out.
$Groups = Get-AdGroup -Properties * -filter * | Where {$_.name -like "www*"}
Foreach($G in $Groups)
{
write-host " "
write-host $G.Name
write-host "----------"
get-adgroupmember -Identity $G | select-object -Property SamAccountName
Out-File -filepath C:\test.txt -Append
}
$output =
Foreach($G in $Groups)
{
write-output " "
write-output $G.Name
write-output "----------"
get-adgroupmember -Identity $G | select-object -Property SamAccountName
}
$output | Out-File "C:\Your\File.txt"
All this does is saves the output of your foreach loop into the variable $output, and then dumps to data to your file upon completion.
Note: Write-Host will write to the host stream, and not the output stream. Changing that to Write-Output will dump it to the $output variable, and subsequently the file upon loop completion.
The reason your original code isn't working is that you're not actually writing anything to the file. In other words, you're piping no data to your Out-File call. Either way, my approach prefers a cache-then-dump methodology.

Output department and direct reports

I am trying to create an "initial" text file that will hold a script run of all users + department + direct reports. My next step after making this file is to create another file the same way but compare it to the original to see if the department for the users ever changed. (not sure yet how to compare the department value just yet)
My current issue is that the department, even though the process is identical to another program I have made in the past, won't print it. Furthermore, when it prints my direct reports it prints only the first one with the whole extension of CN=..., OU=... etc.
I want it to print this way:
username | Department(extensionAttribute14) | Direct Reports (as a single string)
we38432 | IT-Security | cm03456: 04555a: ....etc
My original script used this code for department:
$deps = Get-Aduser -filter {name -like *} -Properties name, extensionAttribute14 | Select name, extensionAttribute14 | Export-CSV $listing -notypeinformation
and this worked. I tried the {name -like *} but that gave me errors in my current program. I know the Export-CSV makes it work but I can't use this format anymore.
for the direct reports my original was this:
foreach ($ID in $directReports){
if ($ID -ne $Null){
$directreports = get-aduser $ID
$directreports.name | Out-File $output -Append
}
This code printed line by line the direct reports but I want them all listed in the same excel cell when I send it there.
I have printed a listing of all the members in the past using ":" and it worked but it is not the case with the direct reports listing. I just get errors when I use this format from my other program:
foreach ($member in $empty.members){
$string = $member.substring(3,$member.indexof(",")-3)
$members = $members + ":" + $string
}
I hope someone can help me with my two issues.
Import-Module ActiveDirectory
$documentOld = "C:\Temp\Old_Supervisor_list_mo_yyyy.txt"
Clear-Content $documentOld
$Header = `
"User ID" <#+ "|" + `
"Department" + "|" + `
"Direct Reports"#>
$Header | Out-File $documentOld -Append
$Users = Get-AdUser -Filter * -Properties name, Enabled, Manager, extensionAttribute14 | Select Enabled, name, Manager, extensionAttribute14
foreach ($user in $Users){
if ($user.enabled –eq $true) {
$name = $user.name
$directReports = Get-ADUser -Identity $name -Properties directreports | Select -ExpandProperty directreports
$department = $user.extensionAttribute14
foreach ($ID in $directReports){
if ($ID -ne $Null){
$directreports = get-aduser $ID
# $string = $directreports + ":"
}#end if $ID
}#end foreach $ID
$listing = `
$name + "|" + $deparment + "|" + $directreports#$string
$listing | Out-File $documentOld -Append
}# end if
}# end foreach $user
Let see if we can make this a little easier and efficient.
Import-Module ActiveDirectory
$documentOld = "C:\Temp\Old_Supervisor_list_mo_yyyy.txt"
$Users = Get-AdUser -Filter * -Properties name,Enabled,Manager,extensionAttribute14 | Where-Object{$_.Enabled}
$Users | ForEach-Object{
$props = #{
Name = $_.Name
Department = $_.extensionAttribute14
DirectReports = ($_.Manager | Where-Object{$_} | ForEach-Object{Get-Aduser $_ | Select-object -ExpandProperty Name}) -join ":"
}
New-Object -TypeName psobject -Property $props
} | Select-Object Name,Department,DirectReports | Export-CSV -Delimiter "|" -NoTypeInformation -Path $documentOld
First we get all the users from your directory with Get-AdUser -Filter * taking all the properties outside the norm that we want. Since you just wanted accounts that are enabled we filter those out now with Where-Object{$_.Enabled}.
The fun part is creating the custom object array ( which is necessary for input for Export-CSV). Create a small hashtable called $props where we set the properties by their friendly names. The special one being DirectReports where we take all the users manager DN's ( Assuming they have one where is what Where-Object{$_} does by filtering out nulls/empty strings.) and use Get-Aduser to get there names. Since you could have more than one manager an array is most likely returned we use -join to ensure only a single string is given for the DirectReports property. That property collection is created for every user and it is then used to create a New-Object which is sent to the output stream.
The Select-Object that follows is just to ensure the order of columns in the CSV that is created. No need for making a CSV file with lots of Out-Files when Export-CSV and -Delimiter "|" will do the hard work for you.