I'm trying to write 8 variables into an CSV file with PowerShell, but it just ends up as ,,,,,,, instead of var1,var2,var3,var4,var5,var6,var7,var8
My code is as follows:
$newRow = "{0},{1},{2},{3},{4},{5},{6},{7}" -f $var1,$var2,$var3,$var4,$var5,$var6,$var7,$var8
$newRow = $newRow -Replace "`t|`n|`r",""
$newRow = $newRow -Replace " ;|; ",";"
$newRow += "`n"
$newRow | Export-Csv -Path $file -Append -noType -Force
Without -Force I get the following error message:
Export-Csv : Cannot append CSV content to the following file: C:\result.txt. The
appended object does not have a property that corresponds to the following column:
var1. To continue with mismatched properties, add the -Force parameter, and then
retry the command.
At C:\Test.ps1:72 char:12
+ $newRow | Export-Csv -Path $file -Append -noType
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (var1:String) [Export-Csv], InvalidOperationException
+ FullyQualifiedErrorId : CannotAppendCsvWithMismatchedPropertyNames,Microsoft.PowerShell.Commands.ExportCsvCommand
EDIT:
Script:
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "powershell.exe"
$startInfo.Arguments = 'C:\zabbix\script\zabbix_vbr_job.ps1 "Discovery"'
$startInfo.RedirectStandardOutput = $true
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $false
#$startInfo.Username = "DOMAIN\Username"
#$startInfo.Password = $password
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$discoveryJson = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()
cls
$discovery = $discoveryJson | ConvertFrom-Json
$file = "C:\zabbix\script\result.txt"
function RunScript ($param, $id)
{
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
$startInfo.FileName = "powershell.exe"
$startInfo.Arguments = "C:\zabbix\script\zabbix_vbr_job.ps1 '$param' '$id'"
$startInfo.RedirectStandardOutput = $true
$startInfo.UseShellExecute = $false
$startInfo.CreateNoWindow = $false
$process = New-Object System.Diagnostics.Process
$process.StartInfo = $startInfo
$process.Start() | Out-Null
$output = $process.StandardOutput.ReadToEnd()
$process.WaitForExit()
return $output
}
$fileContent = Import-csv $file
$NewCSVObject = #()
foreach($obj in $discovery.data)
{
$index = [array]::indexof($discovery.data, $obj)
Write-Host $index "/" $discovery.data.count
#Write-Host (RunScript "Result" $obj.JOBID )
$Result = RunScript "Result" $obj.JOBID
#Write-Host $Result
$RunStatus = RunScript "RunStatus" $obj.JOBID
#Write-Host $RunStatus
$IncludedSize = RunScript "IncludedSize" $obj.JOBID
#Write-Host $IncludedSize
$ExcludedSize = RunScript "ExcludedSize" $obj.JOBID
#Write-Host $ExcludedSize
$VmCount = RunScript "VmCount" $obj.JOBID
#Write-Host $VmCount
$Type = RunScript "Type" $obj.JOBID
#Write-Host $Type
$RunningJob = "RunningJob"#RunScript "RunningJob" $obj.JOBID
#Write-Host $RunningJob
#$newRow = New-Object PsObject -Property #{ JobID = $obj.JOBID ; Result = $Result ; RunStatus = $RunStatus ; IncludedSize = $IncludedSize ; ExcludedSize = $ExcludedSize ; VmCount = $VmCount ; Type = $Type ; RunningJob = $RunningJob }
$newRow = "{0},{1},{2},{3},{4},{5},{6},{7}" -f $obj.JOBID,$Result,$RunStatus,$IncludedSize,$ExcludedSize,$VmCount,$Type,$RunningJob
$newRow = $newRow -Replace "`t|`n|`r",""
$newRow = $newRow -Replace " ;|; ",";"
$newRow += "`n"
#$newRow | Out-File $file
#[io.file]::WriteAllText("C:\zabbix\script\test.txt",$newRow)
Write-Host $newRow
$newRow | Export-Csv -Path $file -Append -noType
break
}
#cls
Write-Host $fileContent
CSV headers:
JobID,Result,RunStatus,IncludedSize,ExcludedSize,VmCount,Type,RunningJob
There is no point in using Export-Csv if you're building the CSV line by hand anyway.
Either change
$newRow | Export-Csv -Path $file -Append -noType -Force
into
$newRow | Add-Content $file
or build $newRow like this:
$newRow = New-Object -Type PSObject -Property #{
'JobID' = $var1
'Result' = $var2
'RunStatus' = $var3
'IncludedSize' = $var4
'ExcludedSize' = $var5
'VmCount' = $var6
'Type' = $var7
'RunningJob' = $var8
}
and the problem will disappear.
The reason for this behavior is that Export-Csv is for transforming objects into a tabular string representation of their properties. Essentially, an object
#{
propertyA: 'foo'
propertyB: 23
}
becomes
propertyA,propertyB
"foo","23"
If you're already building a string, the resulting (string) object has just a single property (Length), which doesn't match any of the properties from your existing CSV. Hence the error you're getting without -Force. Even if you use -Force, the properties written to the CSV are determined from the first item in the existing CSV. Properties that are not present in this set are omitted from the output, and properties from that set that are not present in the object are filled with null values.
Related
I need some help expanding on this script. Basically, I need to add when the $inputFileName = 'Report.txt' is not in its default dir when the script runs every morning the job spits out an e-mail saying no file to process, and then the job stops. What I am seeing is when there is no file in the source dir every now and then, the job runs attaching a blank .xls file that is of no use. I appreciate ANY help in advance!
$ErrorActionPreference="SilentlyContinue"
Stop-Transcript | out-null
$ErrorActionPreference = "Continue"
Start-Transcript -path C:\PATH\Logoutput.txt -append
$emailFilePath = 'C:\PATH\PATH\'
$inputFilePath = 'C:\PATH\PATH\Upload'
$inputFileName = 'Report.txt'
$inputFile = Join-Path $inputFilePath $inputFileName
$outputFileName = 'Report.csv'
$outputFilePath = 'C:\PATH\PATH\Send'
$OutputFile = Join-Path $outputFilePath $outputFileName
$folder = 'C:\PATH\PATH\Upload'
$filter = 'Report.txt'
$destination = $outputFilePath
$dateTime1 = Get-Date -Format s
Write-Host "The file was received: $dateTime1"
Import-CSV $inputFile -Delimiter "`t" -Header "Value 1" , "Value 2" , "Value 3" , "Value 4" , "Value 5" | Tee-Object -Variable Report.txt | Export-CSV $OutputFile -Delimiter "," -NoTypeInformation
$xl = new-object -comobject excel.application
$xl.visible = $false
$Workbook = $xl.workbooks.open("$OutputFile")
$Worksheets = $Workbooks.worksheets
$Workbook.SaveAs("$outputFilePath\Report.xls",1)
$Workbook.Saved = $True
$xl.Quit()
$objExcel = new-object -comobject excel.application
$objExcel.Visible = $false
$objWorkbook = $objExcel.Workbooks.open("$outputFilePath\Report.xls")
$objWorksheet = $objWorkbook.Worksheets.Item(1)
$objRange = $objWorksheet.UsedRange
[void] $objRange.EntireColumn.Autofit()
$objWorkbook.SaveAs("$outputFilePath\Report.xlsx",51)
$objWorkbook.Saved = $True
$objExcel.Quit()
$fromaddress = "user#domain.com"
$toaddress = "user#domain.com"
$bccaddress = ""
$CCaddress = "user#domain.com"
$Subject = "Here Is The Report"
$body = get-content $emailFilePath\content.htm
$attachment = "$outputFilePath\Report.xlsx"
$smtpserver = "smtpdomain"
$message = new-object System.Net.Mail.MailMessage
$message.From = $fromaddress
$message.To.Add($toaddress)
$message.CC.Add($CCaddress)
#$message.Bcc.Add($bccaddress)
$message.IsBodyHtml = $True
$message.Subject = $Subject
$attach = new-object Net.Mail.Attachment($attachment)
$message.Attachments.Add($attach)
$message.body = $body
$smtp = new-object Net.Mail.SmtpClient($smtpserver)
$smtp.Send($message)
$dateTime2 = Get-Date -Format s
Write-Host "The file was parsed and emailed at $dateTime2"
Start-Sleep -Seconds 60
$message.Dispose()
Start-Sleep -Seconds 60
kill -processname Excel
Start-Sleep -Seconds 60
Remove-Item "C:\PATH\PATH\Send\*.*"
$filenameFormat = "Report" + "" + (Get-Date -Format "yyyy-MM-dd") + ".txt"
$updatedFile = "C:\PATH\PATH\Upload\" + $filenameFormat
Rename-Item -Path "C:\PATH\PATH\Upload\Report.txt" -NewName $filenameFormat
Move-Item -Path $updatedFile -Destination C:\PATH\PATH\ArchivedReportData
Stop-Transcript
exit
You need to test for the file before processing as follows:
If ( -not (Test-Path -Path "inputFile")) {
#Write your file not found logic/messages here
Exit
}
Import-CSV $inputFile -Delimiter "`t" -Header "Value 1" , "Value 2" , "Value 3" , "Value 4" , "Value 5" | Tee-Object -Variable Report.txt | Export-CSV $OutputFile -Delimiter "," -NoTypeInformation
$dateTime1 = Get-Date -Format s
Write-Host "The file was received: $dateTime1"
...
HTH
I have a powershell script which reads file and folder info and adds data to an array.
The script runs fine if there is a breakpoint present. The breakpoint can be anywhere in the script and as soon as it is reached and I continue the execution the code finishes without error.
Even if I set the breakpoint to Disabled with Get-PSBreakpoint | Disable-PSBreakpoint the line before the breakpoint the code executes without error.
When the breakpoint is removed the code errors with "Method invocation failed because [System.ManagementAutomation.PSObject] doesn't contain method named op_Addition when adding to the array.
The error occurs here
$report += New-Object -TypeName PSObject -Property $props
and here
$filerec += New-Object -TypeName PSObject -Property $propsfile
This is the function where the error occurs
function ScanDrive([string]$scanpath)
{
#Scan the drive
$files = Get-ChildItem -Path $scanpath -Force -Recurse
$filecount = $files.Count
$Counter = 0
#Set up the result array
$folderRes = #()
$filesRes = #()
ForEach ($file in $files)
{
$lblInfo.Text = $file.FullName
$Form.Refresh()
$Counter++
[Int]$Percentage = ($Counter/$filecount)*100
$pbScan.Value = $Percentage
Write-Debug "Help"
if ($file.Attributes -eq 'Directory')
{
write-host 'This is a folder' + $file.FullName -ForegroundColor DarkGreen
try
{
$acl = Get-Acl -path $file.PSPath
Write-Host 'ACL' + $acl.Group.ToString()
$access = $acl.access
foreach($ar in $access)
{
$props = [ordered]#{
'ID' = ''
'FolderName' = $file.Name;
'ADGroupUser' = $ar.IdentityReference;
'Permissions' = $ar.FileSystemRights.ToString();
'ControlType' = $ar.AccessControlType;
'IsInherited' = $ar.IsInherited
}
$report += New-Object -TypeName PSObject -Property $props
$folderRes += $report
}
Write-Host "Folder record count : " + $folderRes.Count.ToString()
}
catch
{
Write-Host '******************************' -ForegroundColor Yellow
Write-Host 'Error ' + $_.Exception.Message -ForegroundColor Red
Write-Host '******************************' -ForegroundColor Yellow
}
}
else
{
write-host 'This is a file' + write-host $file.FullName -ForegroundColor DarkGreen
$iname = $file.Name
$iparent = $file.PSParentPath
$isize = $file.Length
$itype = $file.Extension
$iowner = get-acl $file.FullName
$owner = $iowner.Owner
$icreated = $file.CreationTime
$ilasmod = $file.LastWriteTime
$idirectory = $file.Directory
$ireadonly = $file.IsReadOnly
$ilastacc = $file.LastAccessTime
$ifilename = $file.FullName
$usergroup = $iowner.Group
$iatts = $file.Attributes
$propsfile = [ordered]#{
'ID' = ''
'Name' = $iname;
'Parent' = $iparent;
'Size' = $isize;
'Type' = $itype;
'Owner' = $owner;
'CreatedDate' = $icreated;
'LastModDate' = $ilasmod;
'Directory' = $idirectory;
'IsReadOnly' = $ireadonly;
'LastAccessedDate' = $ilastacc;
'FileName' = $ifilename;
'UserGroup' = $usergroup;
'Attributes' = $iatts;
'LoggedDate' = Get-Date
}
$filerec += New-Object -TypeName PSObject -Property $propsfile
$filesRes += $filerec
}
}
Write-Host "File record count : " + $filesRes.Count.ToString()
Return $filesRes,$folderRes
}
You don't need to add to $filerec and $report, you can just overwrite them (change += to =):
$filerec = New-Object -TypeName PSObject -Property $propsfile
$report = New-Object -TypeName PSObject -Property $props
Current code tries to create an array of objects and then add it to another array which would create duplicates in $folderRes and $filesRes.
We extracted the required tables from word doc, but can someone help... that how can I export this table object $LETable to CSV, or export values which we have fetched below in CSV in table format.
$objWord = New-Object -Com Word.Application
$filename = 'D:\Files\Scan1.doc'
$objDocument = $objWord.Documents.Open($filename)
$LETable = $objDocument.Tables.Item(4)
$LETableCols = $LETable.Columns.Count
$LETableRows = $LETable.Rows.Count
$obj = New-Object -TypeName PSCustomObject
Write-Output "Starting to write... "
for($r=1; $r -le $LETableRows; $r++) {
for($c=1; $c -le $LETableCols; $c++) {
#Write-Host $r "x" $c
$content = $LETable.Cell($r,$c).Range.Text
Write-Host $content
}
}
$objDocument.Close()
$objWord.Quit()
# Stop Winword Process
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
Not quite ready a solution but I have no more time ATM.
The script outputs all fields enclosed in double quotes and delimited with commas per row and stores in a variable $RawCSV which then is passed to ConvertFrom-Csv
I had trouble with a cr/lf and char 7 in the cell values which I replace with nothing
## Q:\Test\2018\07\17\SO_51385204.ps1
$CsvName = '.\Test.csv'
$filename = (Get-Item ".\Test-text.docx").FullName
$tableNum = 4
$delimiter = ','
$objWord = New-Object -Com Word.Application
$objWord.Visible = $true # $false
$objDocument = $objWord.Documents.Open($filename)
$LETable = $objDocument.Tables.Item($tableNum)
$LETableCols = $LETable.Columns.Count
$LETableRows = $LETable.Rows.Count
Write-Output "Starting to write... "
# "Table rows:{0} cols:{1}" -f $LETableRows,$LETableCols
$RawCSV = for($r=1; $r -le $LETableRows; $r++) {
$content= #()
for($c=1; $c -le $LETableCols; $c++) {
#Write-Host ("R:{0},C:{1}" -f $r,$c)
$content += ("`"{0}`"" -f $LETable.Cell($r,$c).Range.Text -replace "(`r|`n|`t)|$([char]7)?")
}
$Content -join $delimiter
}
$Csv = $RawCSV | ConvertFrom-Csv
$objDocument.Close()
$objWord.Quit()
# Stop Winword Process
$rc = [System.Runtime.Interopservices.Marshal]::ReleaseComObject($objWord)
$Csv
$Csv | Export-Csv $CsvName -NoTypeInformation
I have a powershell script I wrote to check the contents of a folder and if there is a file with a LastWriteTime older than 20 minutes to notify me. The problem I am having is when I get the results it is including all of the files in the email body. How would I write this to get only the latest filename in the email body?
$src = 'c:\test'
$sendmail = $false
Get-ChildItem -path $src | ForEach-Object {
#write-host $_.fullname
$dtdiff = New-TimeSpan ($_.LastWriteTime) $(Get-Date)
if ($dtdiff.TotalMinutes -gt 20){
$strbody=$strbody +$_.fullname+ " Last File Modified at " +$_.LastWriteTime +"`r`n"
$sendmail=$true
}
}
#$strbody
if($sendmail -eq $true){
# Email components
$strFromAddress = "abc#xyz.net"
$strToAddress = "abc#xyz.net"
$strMessageSubject = "REPORT"
$strMessageBody = $strbody
$strSendingServer = "smtp.com"
$SMTPPort = "587"
$emailSmtpUser = "abc#xyz.net"
$emailSmtpPass = "test123"
# Email objects
$objSMTPMessage = New-Object System.Net.Mail.MailMessage $strFromAddress, $strToAddress, $strMessageSubject, $strMessageBody
$objSMTPClient = New-Object System.Net.Mail.SMTPClient( $strSendingServer, $SMTPPort )
$objSMTPClient.EnableSsl = $true
$objSMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );
$objSMTPClient.Send($objSMTPMessage)
}
To get only the most actual file: Edited to remove flaw
Get-ChildItem -path $src |
Sort LastWriteTime |
Select -last 1 |
ForEach-Object {
#write-host $_.fullname
$dtdiff = New-TimeSpan ($_.LastWriteTime) $(Get-Date)
if ($dtdiff.TotalMinutes -gt 20){
$strbody=$strbody +$_.fullname+ " Last File Modified at " +$_.LastWriteTime +"`r`n"
$sendmail=$true
}
}
You're appending each filename and its associated timestamp to $strbody in that loop. It's doing exactly what you specified.
If you want only the latest file that was created in the last 20 minutes, change your get-childitem/foreach loop to this:
$mostrecentfile = get-childitem -path $src |
where-object {$_.lastwritetime -gt (get-date).addminutes(-20)} |
sort-object -property lastwritetime -descending | select-object -first 1
}
if ($mostrecentfile -ne $null) {
$strbody=$_.fullname+ " Last File Modified at " +$_.LastWriteTime;
$sendmail = $true;
}
get-childitem "c:\temp" -file | where LastWriteTime -le (Get-Date).AddMinutes(-20) |
Sort lastwritetime -descending |
% {
$strFromAddress = "abc#xyz.net"
$strToAddress = "abc#xyz.net"
$strMessageSubject = "REPORT"
$strMessageBody = "Last file modifed '{0}' at {1}" -f $_.fullname, $_.LastWriteTime
$strSendingServer = "smtp.com"
$SMTPPort = "587"
$emailSmtpUser = "abc#xyz.net"
$emailSmtpPass = "test123"
# Email objects
$objSMTPMessage = New-Object System.Net.Mail.MailMessage $strFromAddress, $strToAddress, $strMessageSubject, $strMessageBody
$objSMTPClient = New-Object System.Net.Mail.SMTPClient($strSendingServer, $SMTPPort )
$objSMTPClient.EnableSsl = $true
$objSMTPClient.Credentials = New-Object System.Net.NetworkCredential( $emailSmtpUser , $emailSmtpPass );
$objSMTPClient.Send($objSMTPMessage)
$objSMTPClient.Dispose()
break
}
From within PowerShell, I know how to send a basic email. But with my syntax below, how could I append to the body of the email each $QueryName and each $RowCount and add a hyperlink to the value contained in $FPath\$FormattedDate\so the body of email would look like this:
$QueryName - $RowCount
(or with actual data)
Santa - 14
Mickey - 12
Mars - 2
Here is my current PS script
Function Execute-SQLquery {
param ($QueryName, $QueryString)
$server = "Server"
$database = "DB1"
$FPath = "C:\Testing"
#Setting additional variables
$extension = ".csv"
$date = Get-Date -f 'MM.dd.yy'
$FormattedDate = Get-Date -f 'MM.dd.yy'
$connectionTemplate = "Data Source={0};Integrated Security=SSPI;Initial Catalog={1};"
$connectionString = [string]::Format($connectionTemplate, $server, $database)
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$command = New-Object System.Data.SqlClient.SqlCommand
$command.CommandText = $QueryString
$command.Connection = $connection
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
$SqlAdapter.SelectCommand = $command
$DataSet = New-Object System.Data.DataSet
$rowCount = $SqlAdapter.Fill($DataSet)
if(!(Test-Path -path "$FPath\$FormattedDate\")){New-Item "$FPath\$FormattedDate\" -type directory}
if ($rowCount -gt 0)
{
if ($QueryName -eq "Santa")
{
$extractFile = "C:\Testing\TemplateFiles\Santa.csv"
[System.IO.Directory]::CreateDirectory("$FPath\$FormattedDate\Santa\")
Write-Host $rowCount -fore Red
$dirName = "$FPath\$FormattedDate\Santa\"
$filename = [IO.Path]::GetFileNameWithoutExtension($extractFile) + "_$date" + [IO.Path]::GetExtension($extractFile)
$extractFile = Join-Path $dirname $filename
}
if ($QueryName -eq "Mickey")
{
$extractFile = "C:\Testing\TemplateFiles\Mickey.csv"
[System.IO.Directory]::CreateDirectory("$FPath\$FormattedDate\Mickey\")
Write-Host $rowCount -fore Red
$dirName = "$FPath\$FormattedDate\Mickey\"
$filename = [IO.Path]::GetFileNameWithoutExtension($extractFile) + "_$date" + [IO.Path]::GetExtension($extractFile)
$extractFile = Join-Path $dirname $filename
}
if ($QueryName -eq "Mars")
{
$extractFile = "C:\Testing\TemplateFiles\Mickey\Mars.csv"
[System.IO.Directory]::CreateDirectory("$FPath\$FormattedDate\Mars\")
Write-Host $rowCount -fore Red
$dirName = "$FPath\$FormattedDate\Mars\"
$filename = [IO.Path]::GetFileNameWithoutExtension($extractFile) + "_$date" + [IO.Path]::GetExtension($extractFile)
$extractFile = Join-Path $dirname $filename
}
$DataSet.Tables[0] | Export-Csv $extractFile -NoTypeInformation
}
$connection.Close()
}
First up, since the only thing that changes based on $QueryName are direct references to the value in $QueryName and the $extractFile, you'd be better off not repeating that entire block.
For the mail message, you can use Send-MailMessage.
To add a link to a local file resource, use the file:/// scheme prefix and change all backslashes (\) to forward slashes (/), ie. file:///C:/Document/Path.ext, or in your example "file:///$("$FPath\$FormattedDate" -replace '\','/')":
Function Execute-SQLquery {
param ($QueryName, $QueryString)
# up to this point no change is required
if ($rowCount -gt 0)
{
$extractFile = switch($QueryName){
"Santa" { "C:\Testing\TemplateFiles\Santa.csv" }
"Mickey" { "C:\Testing\TemplateFiles\Mickey.csv" }
"Mars" { "C:\Testing\TemplateFiles\Mars\Mickey.csv" }
default { throw "Illegal QueryName" }
}
[System.IO.Directory]::CreateDirectory("$FPath\$FormattedDate\$QueryName\")
Write-Host $rowCount -fore Red
$dirName = "$FPath\$FormattedDate\$QueryName\"
$filename = [IO.Path]::GetFileNameWithoutExtension($extractFile) + "_$date" + [IO.Path]::GetExtension($extractFile)
$extractFile = Join-Path $dirname $filename
$DataSet.Tables[0] | Export-Csv $extractFile -NoTypeInformation
$EmailBody = #'
Here are the results:
{0} - {1}
Find the documents here
'# -f $QueryName,$rowCount,$("$FPath\$FormattedDate" -replace '\','/')
Send-MailMessage -From "me#company.example" -To "you#company.example" -Body $EmailBody -BodyAsHtml:$true -Subject "Data extracted!" -SmtpServer "your.mail.server.company.example"
}
$connection.Close()
}