Match user from list against filenames - csv

I have a long winded script that gets a user from an CSV file, matches user against a CSV filename from a specific directory.
The user is matched against this CSV file in this format <data><samaccountname><text>.csv
The aim here is to get an AD User from a list, then scan a folder with CSV Files in it and match against the user. From there restore the user AD attributes.
The issue here is that the output is always of the last user twice, I have REM out the export at the end so I can see what is on screen first.
Clear-Host
#Get username from users list and match against CSV file name.
$FDate = (get-date).ToString("yyyMMdd")
$Project = "<FolderPath>" #Project name used to setup folders and for reports etc
$ProjectRoot = "<path>\" # Backup folder
$RestorePath = $ProjectRoot + $Project #combined path for restoring
$UsersListFile = $ProjectRoot + '\Userlist.csv' #Userlist
$Results = #{} # Storage for all csv files
$PSObject = New-Object psobject
$Report = #() #For Export-CSV
$Results = gci $RestorePath -Filter '*.csv'
$i = 0
foreach ($File in $Results) {
$i += 1
Write-Host 'Number of passes - '$i
Write-Host 'Current file processing - '$file.Name -for Green
foreach ($User in (import-csv $UsersListFile)) {
$SAM = $User.SamAccountName
Write-Host 'Current User processing - '$SAM -ForegroundColor Magenta
if ($file.Name -match $SAM) {
Write-host "Filename and user $SAM match " -for Yellow
$Row= New-Object psobject
$ROW | Add-Member -type NoteProperty -name Name -value $SAM -force
$Report += $Row
foreach ($Attrib in (import-csv $restorepath\$file)) {
#Write-host 'Attributes in file - ' $attrib.samaccountname $Attrib.mail -for Yellow
#Use this to restore AD User data
}
} else {
Write-Host "No match" -ForegroundColor Red
}
}
}
#$Report | Export-Csv $RestorePath'\Test.csv' -NoTypeInformation -Force
$Report | Sort-Object Name

Updated script to move New-Object psobject to above $Row, so this creates a new object each time, rather then overwriting previous entry.

Related

Function not Returning Data

I am using PowerShell to compare the file count and size (per file extension) in 2 separate directories.
$User = $env:username
$pwd = pwd
clear
write-host "`n"
write-host "`n"
write-host "`n"
write "The current user is: $User"
write-host "`n"
write "The current path is: $pwd"
write-host "`n"
write-host "`n"
write-host "`n"
write "We need to know the following information:"
write "`n"
write "`n"
$UserDesktopPath = Read-Host "New PC User Desktop Path" # This should be the new PC Desktop Path
$UserDocumentPath = Read-Host "New PC User Document Path" # This should be the new PC Document Path
$USBDesktopPathServer = Read-Host "USB User Desktop Path" # This should be the USB User Desktop Path
$USBDocumentPathServer = Read-Host "USB User Document Path" # This should be the USB User Document Path
clear
write-host "`n"
write-host "`n"
write-host "`n"
write "This is the results for your Desktop Folder Paths:"
write-host "`n"
$folder_new = Get-ChildItem -Recurse -path "$USBDesktopPathServer" # Recurses the New PC Desktop
$folder_old = Get-ChildItem -Recurse -path "$UserDesktopPath" # Recurses the USB Backup Desktop
Compare-Object -ReferenceObject "$folder_new" -DifferenceObject "$folder_old" # Compares the two folders for the path to identify discrepancies
write-host "`n"
write "This is the results for your Documents Folder Paths:"
write-host "`n"
write-host "`n"
write-host "`n"
$folder_new1 = Get-ChildItem -Recurse -path "$UserDocumentPath" # Recurses the New PC Documents
$folder_old1 = Get-ChildItem -Recurse -path "$USBDocumentPathServer" # Recurses the USB Backup Documents
Compare-Object -ReferenceObject "$folder_new1" -DifferenceObject "$folder_old1" # Compares the two folders for the path to identify discrepancies
write-host "`n"
write-host "`n"
write-host "`n"
write "Now we shall compare file sizes of your Documents:"
write-host "`n"
write-host "`n"
write-host "`n"
write-host "`n"
function doc{
$DirectoryDocuments = "$USBDocumentPathServer", "$UserDocumentPath"
foreach ($Directory in $DirectoryDocuments) {
Get-ChildItem -Path $Directory -Recurse |
Where-Object {-not $_.PSIsContainer} |
Tee-Object -Variable Files |
Group-Object -Property Extension |
Select-Object -Property #{
n = "Directory"
e = {$Directory}
},
#{
n = "Extension"
e = { $_.Name -replace '^\.' }
},
#{
n = "Size (MB)"
e={ [math]::Round( ( ( $_.Group | Measure-Object Length -Sum ).Sum / 1MB ), 2 ) }
},
Count
$Files |
Measure-Object -Sum -Property Length |
Select-Object -Property #{
n = 'Extension'
e = { 'Total' }
},
#{
n = 'Size (MB)'
e = { [math]::Round( ( $_.Sum / 1MB ), 2 ) }
},
Count
}
}
When using the ISE and calling dtop I get the correct return:
PS C:\Users\Michael Nancarrow> dtop
Directory Extension Size (MB) Count
--------- --------- --------- -----
D:\Deployment Kit\Test\Desktop2 txt 0 1
Total 0 1
D:\Deployment Kit\Test\Desktop1 txt 0 11
Total 0 11
Yet when run in the script, it does not return any value. I have attempted to call a function write $tst which runs dtop and that does the same (writes null).
Furthermore, I have removed the { so it does not run as a function, and it operates without an issue. My concern is perhaps the -Path file cannot be parsed at the same time as the input - meaning: when I call dtop from ISE it already has the $Directory variable stored in memory.
Are there any obvious errors here? I am rather new to PowerShell and am unsure where the mistake lies.

Can't export csv in powershell, empty CSV file

I'm trying to get what permissions files and folders have and export to a csv file. I can get the info to display on screen, but when I try to export it the resulting csv file is empty.
The code:
function Test-IsWritable(){
<#
.Synopsis
Command tests if a file is present and writable.
.Description
Command to test if a file is writeable. Returns true if file can be opened for write access.
.Example
Test-IsWritable -path $foo
Test if file $foo is accesible for write access.
.Example
$bar | Test-IsWriteable
Test if each file object in $bar is accesible for write access.
.Parameter Path
Psobject containing the path or object of the file to test for write access.
#>
[CmdletBinding()]
param([Parameter(Mandatory=$true,ValueFromPipeline=$true)][psobject]$path)
process{
Write-Host "Test if file $path is writeable"
if (Test-Path -Path $path -PathType Any){
$target = Get-Item $path -Force
try{
$writestream = $target.Openwrite()
$writestream.Close() | Out-Null
Remove-Variable -Name writestream
Write-Host "File is writable" -ForegroundColor DarkGreen
Write-Output $true
}
catch{
Write-Host "File is not writable" -ForegroundColor DarkRed
Write-Output $false
}
Remove-Variable -Name target
}
else{
Write-Host "File $path does not exist or is a directory" -ForegroundColor Red
Write-Output $false
}
}
}
write-host "WARNING: If checking deep folders (where the full path is longer than 248 characters) please " -foregroundcolor Yellow -NoNewline
Write-Host "MAP THE DRIVE " -ForegroundColor Red -NoNewline
Write-Host "in order to keep the names as short as possible" -ForegroundColor Yellow
$basefolder = Read-Host -Prompt 'What is the folder or files you want to get permissions of?'
write-host "WARNING: if permissions.csv already exists, it will be overwritten!" -foregroundcolor Yellow
Write-Host 'Export results to CSV? (y/n): ' -ForegroundColor Magenta -NoNewline
$export = Read-Host
if ($export -like "y")
{
Write-Host "Name the file (ex: permissions.csv): " -ForegroundColor Magenta -NoNewline
$FileName = Read-Host
$Outfile = “$PSScriptRoot\$FileName”
write-host "Will write results to $PSScriptRoot\$FileName" -ForegroundColor Green
}
else
{
write-host "User did not type 'y', continuing" -ForegroundColor DarkYellow
}
$files = get-childitem $basefolder -recurse -File
Write-Host $files
Write-Host "=========================" -ForegroundColor Black
#$subfiles = Get-ChildItem $folders -Recurse -File
#Write-Host $folders
#Write-Host "=========================" -ForegroundColor Black
#Write-Host $subfiles
$results = foreach($folder in $files) {
New-Object psobject -Property #{
File = $folder;
Access = "$basefolder\$folder" | Test-IsWritable
}
Write-Host $folder
}
#$subresults = foreach($subfile in $subfiles) {
# New-Object psobject -Property #{
# File = $subfiles;
# Access = $subfile | Test-IsWritable;
# }
#}
Write-Host $results
Write-Host "Finished combo loop, exporting..." -ForegroundColor Green
$results | Export-Csv $Outfile -NoTypeInformation -Delimiter ";"
Write-Host "Converting delimited CSV to Column Excel Spreadsheet"
$outputXLSX = $PSScriptRoot + "\$Filename.xlsx"
$excel = New-Object -ComObject excel.application
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
$TxtConnector = ("TEXT;" + $Outfile)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = ';'
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,2 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
$query.Refresh()
$query.Delete()
$Workbook.SaveAs($outputXLSX,51)
$excel.Quit()
Remove-Item $Outfile
Write-Host "See $PSScriptRoot\$Filename.xlsx for results" -ForegroundColor Green
UPDATE: Mostly working, strange output though:
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file1.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file2.txt
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
Z:\testfolder\file3.rar
The specified path, file name, or both are too
long. The fully qualified file name must be less than 260 characters,
and the directory name must be less than 248 characters.
In the next column:
FileAccess
FullControl
FullControl
FullControl
Modify, Synchronize
ReadAndExecute, Synchronize
Modify, Synchronize
Modify, Synchronize
FullControl
FullControl
FullControl
Modify, Synchronize
...
The specified path, file name, or both are too long. The fully
qualified file name must be less than 260 characters, and the
directory name must be less than 248 characters.
I'm not sure why it's showing multiple rows for the same file, I'd like to have 1 row per file with the true File Access.
Remove Write-Host before using Export-Csv. Write-Hostconsumes the data from the pipeline and only outputs it on screen.
#(...)
$i = 0
$results = foreach($acl in $acls) {
$folder = (Convert-Path $acl.pspath)
Write-Progress -Activity "Getting Security" -Status "checking $folder" -PercentComplete ($i / $folders.Count * 100)
foreach($access in $acl.GetAccessRules($true, $true, [System.Security.Principal.SecurityIdentifier])) {
New-Object psobject -Property #{
Folder = $folder;
User = $acl.Owner;
Group=$acl.Group;
Mode = $access.AccessControlType;
FileAcess = $access.FileSystemRights;
}
}
$i++
}
Write-Host "Reached End, exporting..." -ForegroundColor Green
$results | Export-Csv $Outfile -NoTypeInformation -Delimiter ";"

Import CSV and updating specific lines

So I have a script that runs at logon to search for PST's on a users machine, then copies them to a holding area waiting for migration.
When the search/copy is complete it outputs to a CSV that looks something like this:
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
The same logon script has an IF to import the CSV if the copied status is in use and makes further attempts at copying the PST into the holding area. If it's successful it exports the results to the CSV file.
My question is, is there anyway of getting it to either amend the existing CSV changing the copy status? I can get it to add the new line to the end, but not update.
This is my 'try again' script:
# imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
ForEach ( $PST_USE in $PST_IN_USE )
{ $NAME = Get-ItemProperty $PST_IN_USE.Path | select -ExpandProperty Name
$NEW_NAME = $USER + "_" + $PST_IN_USE.Size_in_MB + "_" + $NAME
# attempts to copy the file to the pst staging area then rename it.
TRY { Copy-Item $PST_IN_USE.Path "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME
# edits the existing csv file replacing "In Use" with "Client Copied"
$PST_IN_USE.Copied -replace "In Use","Client Copied"
} # CLOSES TRY
# silences any errors.
CATCH { }
$PST_IN_USE | Export-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" -NoClobber -NoTypeInformation -Append
} # CLOSES ForEach ( $PST_USE in $PST_IN_USE )
This is the resulting CSV
Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied
COMP1,user1,\\comp1\c$\Test PST.pst,20.58752,08/12/2015,08/12/2015,Client copied
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,In Use
COMP1,user1,\\comp1\c$\outlook\outlook.pst,100,08/12/2015,15,12,2015,Client copied
It's almost certainly something really simple, but if it is, it's something I've yet to come across in my scripting. I'm mostly working in IF / ELSE land at the moment!
If you want to change the CSV file, you have to write it completely again, not just appending new lines. In your case this means:
# Get the data
$data = Import-Csv ...
# Get the 'In Use' entries
$inUse = $data | where Copied -eq 'In Use'
foreach ($x in $inUse) {
...
$x.Copied = 'Client Copied'
}
# Write the file again
$data | Export-Csv ...
The point here is, you grab all the lines from the CSV, modify those that you process and then write the complete collection back to the file again.
I've cracked it. It's almost certainly a long winded way of doing it, but it works and is relatively clean too.
#imports line of csv where PST file is found to be in use
$PST_IN_USE = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -eq "In Use" }
$PST_IN_USE | select -ExpandProperty path | foreach {
# name of pst
$NAME = Get-ItemProperty $_ | select -ExpandProperty Name
# size of pst in MB without decimals
$SIZE = Get-ItemProperty $_ | select -ExpandProperty length | foreach { $_ / 1000000 }
# path of pst
$PATH = $_
# new name of pst when copied to the destination
$NEW_NAME = $USER + "_" + $SIZE + "_" + $NAME
TRY { Copy-Item $_ "\\comp4\TEMPPST\PST\$USER" -ErrorAction SilentlyContinue
TRY { Rename-Item "\\comp4\TEMPPST\PST\$USER\$NAME" -NewName $NEW_NAME -ErrorAction SilentlyContinue | Out-Null }
CATCH { $NEW_NAME = "Duplicate exists" }
$COPIED = "Client copied" }
CATCH { $COPIED = "In use" ; $NEW_NAME = " " }
$NEW_FILE = Test-Path "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
IF ( $NEW_FILE -eq $FALSE )
{ "Hostname,User,Path,Size_in_MB,Creation,Last_Access,Copied,New_Name" |
Set-Content "\\lccfp1\TEMPPST\PST\$HOSTNAME - $USER 4.csv" }
"$HOSTNAME,$USER,$PATH,$SIZE,$CREATION,$LASTACCESS,$COPIED,$NEW_NAME" |
Add-Content "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
} # CLOSES FOREACH #
$a = Import-CSV "\\comp4\TEMPPST\PST\$HOSTNAME - $USER.csv" | where { $_.copied -ne "in use" }
$b = Import-Csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 4.csv"
$a + $b | export-csv "\\comp4\TEMPPST\PST\$HOSTNAME - $USER 8.csv" -NoClobber -NoTypeInformation
Thanks for the help. Sometimes it takes a moments break and a large cup of coffee to see things a different way.

Exporting PowerShell Foreach Loop to HTML

I've been trying to write a script that pings a list of computers from a text file and exports the output to a HTML file.
Using a ForEach loop and and if/else statement I have been able to get a working ping script working that displays in PowerShell but haven't been able to export the results to a html file.
When I run the script the HTML file opens but only displays the line "Here are the ping results for $date"
I'm pretty new to PowerShell so any kind of input or help would be appreciated!
$ComputersAry = Get-Content -Path "C:\Script\ping.txt"
$filepath = "C:\Script\"
$date = "{0:yyy_MM_dd-HH_mm}" -f (get-date)
$file = $filepath + "Results_" + $date + ".htm"
New-Item $filepath -type directory -force -Verbose
$Header = #"
<style>
TABLE {border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;}
TH {border-width: 1px;padding: 3px;border-style: solid;border-color: black;background-color: #6495ED;}
TD {border-width: 1px;padding: 3px;border-style: solid;border-color: black;}
</style>
<title>
LRS Ping Results
</title>
"#
Foreach ($MachineName in $ComputersAry) {
$PingStatus = Gwmi Win32_PingStatus -Filter "Address ='$MachineName'" | Select-Object StatusCode
if($PingStatus.StatusCode -eq 0){
$output = write-host "$MachineName,Ping Success!!,$Date"
} else {
$output = write-host "$MachineName,Ping FAIL, please investigate cause ASAP!!"
}
}
$pre= "Here are the ping results for $date"
$output | Select-Object Name, Status, Date | ConvertTo-HTML -Head $Header -PreContent $pre | Out-File $file
Invoke-Item $file
Nothing gets assigned to $output when you use Write-Host. Try this instead:
$output = "$MachineName,Ping Success!!,$Date"
...
$output = "$MachineName,Ping FAIL, please investigate cause ASAP!!"
Write-Host tells PowerShell to write directly to the host's display. This bypasses the Output (stdout) stream. While you could replace Write-Host with Write-Output almost no one uses Write-Output because the default stream every goes to is the output stream anyway. So when a string like "Hello World" reaches the end of the pipeline and there isn't an Out-File or Out-Printer it will by default get stuck into the Output stream and when the result of the pipeline execution is assigned to a variable, it gets whatever is in the output stream.
Try doing the following, save the code as a script and run it.
PS C:\Scripts> .\Demo.ps1 | ConvertTo-Html | Out-File C:\Scripts\out.htm
$result = '' | Select Online
$ComputersAry = GC C:\Scripts\2.txt
Foreach ($MachineName in $ComputersAry) {
$PingStatus = Gwmi Win32_PingStatus -Filter "Address ='$MachineName'" | Select-Object StatusCode
if($PingStatus.StatusCode -eq 0){
$result.Online = "$MachineName,Ping Success!!"
} else {
$result.Online = "$MachineName,Ping FAIL, please investigate cause ASAP!!"
}
$result
}

Get AD distinguished name

I'm trying to take input from a CSV file, which has a list of group names (canonical names) and get the Distinguished Name from it, then output to another CSV file. The code:
#get input file if passed
Param($InputFile)
#Set global variable to null
$WasError = $null
#Prompt for file name if not already provided
If ($InputFile -eq $NULL) {
$InputFile = Read-Host "Enter the name of the input CSV file (file must have header of 'Group')"
}
#Import Active Directory module
Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
$DistinguishedNames = Import-Csv -Path $InputFile -Header Group | foreach-Object {
$GN = $_.Group
$DN = Get-ADGroup -Identity $GN | Select DistinguishedName
}
$FileName = "RESULT_Get-DistinguishedNames" + ".csv"
#Export list to CSV
$DNarray | Export-Csv -Path $FileName -NoTypeInformation
I've tried multiple solutions, and none have seemed to work. Currently, it throws an error because
Cannot validate argument on parameter 'Identity'. The argument is null. Supply a non-null argument and try the command again.
I tried using -Filter also, and in a previous attempt I used this code:
Param($InputFile)
#Set global variable to null
$WasError = $null
#Prompt for file name if not already provided
If ($InputFile -eq $NULL) {
$InputFile = Read-Host "Enter the name of the input CSV file(file must have header of 'GroupName')"
}
#Import Active Directory module
Import-Module -Name ActiveDirectory -ErrorAction SilentlyContinue
$DistinguishedNames = Import-Csv -Path $InputFile | foreach {
$strFilter = "*"
$Root = [ADSI]"GC://$($objDomain.Name)"
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher($root)
$objSearcher.Filter = $strFilter
$objSearcher.PageSize = 1000
$objsearcher.PropertiesToLoad.Add("distinguishedname") | Out-Null
$objcolresults = $objsearcher.FindAll()
$objitem = $objcolresults.Properties
[string]$objDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
[string]$DN = $objitem.distinguishedname
[string]$GN = $objitem.groupname
#Get group info and add mgr ID and Display Name
$props = #{'Group Name'= $GN;'Domain' = $objDomain;'Distinguished Name' = $DN;}
$DNS = New-Object psobject -Property $props
}
$FileName = "RESULT_Get-DistinguishedNames" + ".csv"
#Export list to CSV
$DistinguishedNames | Sort Name | Export-Csv $FileName -NoTypeInformation
The filter isn't the same one I was using here, I can't find the one I was using, the I currently have is a broken attempt.
Anyway, the main issue I was having is that it will get the group name, but search for it in the wrong domain (it wouldn't include Organizational Units) which caused none of them to be found. When I search for a group in PowerShell though (using Get-ADGroup ADMIN) they show up with the correct DN and everything. Any hints or code samples are appreciated.
You seemingly miss the point of $variable = cmdlet|foreach {script-block} assignment. The objects to assign to $variable should be returned (passed through the script block) in order to end up in $variable. Both your main loops contain the structure of the line $somevar=expectedOutput where expectedOutput is either a New-Object psobject or Get-ADGroup call. The assignment to $someVar suppresses the output, so that the script block does not have anything to return, and $variable remains null. To fix, do not prepend the call that should return an object into outside variable with an assignment.
$DistinguishedNames = Import-Csv -Path $InputFile -Header Group | foreach-Object {
$GN = $_.Group
Get-ADGroup -Identity $GN | Select DistinguishedName # drop '$DN=`
}
$DistinguishedNames | Export-CSV -Path $FileName -NoTypeInformation
The same issue with the second script.