none
Конвертация в html RRS feed

  • Вопрос

  • Доброго времени суток!

    Есть скрипт для парсинга логов:

    param(
    	[parameter(Position=0,Mandatory=$true,ValueFromPipeline=$false,HelpMessage='Source Path with no trailing slash')][string]$SourcePath,
    	[switch]$fp
    	)
     
    write-host "Robocopy log parser. $(if($fp){"Parsing file entries"} else {"Parsing summaries only, use -fp to parse file entries"})"
     
    #Arguments
    # -fp	File parse. Counts status flags and oldest file Slower on big files.
     
    $ElapsedTime = [System.Diagnostics.Stopwatch]::StartNew()
    $refreshrate=1 # progress counter refreshes this often when parsing files (in seconds)
     
    # These summary fields always appear in this order in a robocopy log
    $HeaderParams = @{
    	"04|Started" = "date";	
    	"01|Source" = "string";
    	"02|Dest" = "string";
    	"03|Options" = "string";
    	"07|Dirs" = "counts";
    	"08|Files" = "counts";
    	"09|Bytes" = "counts";
    	"10|Times" = "counts";
    	"05|Ended" = "date"
    	#"06|Duration" = "string"
    }
     
    $ProcessCounts = @{
    	"Processed" = 0;
    	"Error" = 0;
    	"Incomplete" = 0
    }
     
    $tab=[char]9
     
    $files=get-childitem $SourcePath
     
    $writer=new-object System.IO.StreamWriter("$(get-location)\robocopy-$(get-date -format "dd-MM-yyyy_HH-mm-ss").csv")
     
    function Get-Tail([object]$reader, [int]$count = 10) {
     
    	$lineCount = 0
    	[long]$pos = $reader.BaseStream.Length - 1
     
    	while($pos -gt 0)
    	{
    		$reader.BaseStream.position=$pos
     
    		# 0x0D (#13) = CR
    		# 0x0A (#10) = LF
    		if ($reader.BaseStream.ReadByte() -eq 10)
    		{
    			$lineCount++
    			if ($lineCount -ge $count) { break }
    		}
    		$pos--
    	} 
     
    	# tests for file shorter than requested tail
    	if ($lineCount -lt $count -or $pos -ge $reader.BaseStream.Length - 1) {
    		$reader.BaseStream.Position=0
    	} else {
    		# $reader.BaseStream.Position = $pos+1
    	}
     
    	$lines=@()
    	while(!$reader.EndOfStream) {
    		$lines += $reader.ReadLine()
    	}
    	return $lines
    }
     
    function Get-Top([object]$reader, [int]$count = 10)
    {
    	$lines=@()
    	$lineCount = 0
    	$reader.BaseStream.Position=0
    	while(($linecount -lt $count) -and !$reader.EndOfStream) {
    		$lineCount++
    		$lines += $reader.ReadLine()		
    	}
    	return $lines
    }
     
    function RemoveKey ( $name ) {
    	if ( $name -match "|") {
    		return $name.split("|")[1]
    	} else {
    		return ( $name )
    	}
    }
     
    function GetValue ( $line, $variable ) {
     
    	if ($line -like "*$variable*" -and $line -like "* : *" ) {
    		$result = $line.substring( $line.IndexOf(":")+1 )
    		return $result 
    	} else {
    		return $null
    	}
    }
     
    function UnBodgeDate ( $dt ) {
    	# Fixes RoboCopy botched date-times in format Sat Feb 16 00:16:49 2013
    	if ( $dt -match ".{3} .{3} \d{2} \d{2}:\d{2}:\d{2} \d{4}" ) {
    		$dt=$dt.split(" ")
    		$dt=$dt[2],$dt[1],$dt[4],$dt[3]
    		$dt -join " "
    	}
    	if ( $dt -as [DateTime] ) {
    		return $dt.ToStr("dd/MM/yyyy hh:mm:ss")
    	} else {
    		return $null
    	}
    }
     
    function UnpackParams ($params ) {
    	# Unpacks file count bloc in the format
    	#    Dirs :      1827         0      1827         0         0         0
    	#	Files :      9791         0      9791         0         0         0
    	#	Bytes :  165.24 m         0  165.24 m         0         0         0
    	#	Times :   1:11:23   0:00:00                       0:00:00   1:11:23
    	# Parameter name already removed
     
    	if ( $params.length -ge 58 ) {
    		$params = $params.ToCharArray()
    		$result=(0..5)
    		for ( $i = 0; $i -le 5; $i++ ) {
    			$result[$i]=$($params[$($i*10 + 1) .. $($i*10 + 9)] -join "").trim()
    		}
    		$result=$result -join ","
    	} else {
    		$result = ",,,,,"
    	}
    	return $result
    }
     
    $sourcecount = 0
    $targetcount = 1
     
    # Write the header line
    $writer.Write("File")
    foreach ( $HeaderParam in $HeaderParams.GetEnumerator() | Sort-Object Name ) {
    	if ( $HeaderParam.value -eq "counts" ) {
    		$tmp="~ Total,~ Copied,~ Skipped,~ Mismatch,~ Failed,~ Extras"
    		$tmp=$tmp.replace("~","$(removekey $headerparam.name)")
    		$writer.write(",$($tmp)")
    	} else {
    		$writer.write(",$(removekey $HeaderParam.name)")
    	}
    }
     
    if($fp){
    	$writer.write(",Scanned,Newest,Summary")
    }
     
    $writer.WriteLine()
     
    $filecount=0
     
    # Enumerate the files
    foreach ($file in $files) {  
    	$filecount++
        write-host "$filecount/$($files.count) $($file.name) ($($file.length) bytes)"
    	$results=@{}
     
    	$Stream = $file.Open([System.IO.FileMode]::Open, 
                       [System.IO.FileAccess]::Read, 
                        [System.IO.FileShare]::ReadWrite) 
    	$reader = New-Object System.IO.StreamReader($Stream) 
    	#$filestream=new-object -typename System.IO.StreamReader -argumentlist $file, $true, [System.IO.FileAccess]::Read
     
    	$HeaderFooter = Get-Top $reader 16
     
    	if ( $HeaderFooter -match "ROBOCOPY     ::     Robust File Copy for Windows" ) {
    		if ( $HeaderFooter -match "Files : " ) {
    			$HeaderFooter = $HeaderFooter -notmatch "Files : "
    		}
     
    		[long]$ReaderEndHeader=$reader.BaseStream.position
     
    		$Footer = Get-Tail $reader 16
     
    		$ErrorFooter = $Footer -match "ERROR \d \(0x000000\d\d\) Accessing Source Directory"
    		if ($ErrorFooter) {
    			$ProcessCounts["Error"]++
    			write-host -foregroundcolor red "`t $ErrorFooter"
    		} elseif ( $footer -match "---------------" ) {
    			$ProcessCounts["Processed"]++
    			$i=$Footer.count
    			while ( !($Footer[$i] -like "*----------------------*") -or $i -lt 1 ) { $i-- }
    			$Footer=$Footer[$i..$Footer.Count]
    			$HeaderFooter+=$Footer
    		} else {
    			$ProcessCounts["Incomplete"]++
    			write-host -foregroundcolor yellow "`t Log file $file is missing the footer and may be incomplete"
    		}
     
    		foreach ( $HeaderParam in $headerparams.GetEnumerator() | Sort-Object Name ) {
    			$name = "$(removekey $HeaderParam.Name)"
    			$tmp = GetValue $($HeaderFooter -match "$name : ") $name
    			if ( $tmp -ne "" -and $tmp -ne $null ) {
    				switch ( $HeaderParam.value ) {
    					"date" { $results[$name]=UnBodgeDate $tmp.trim() }
    					"counts" { $results[$name]=UnpackParams $tmp }
    					"string" { $results[$name] = """$($tmp.trim())""" }		
    					default { $results[$name] = $tmp.trim() }		
    				}
    			}
    		}
     
    		if ( $fp ) {
    			write-host "Parsing $($reader.BaseStream.Length) bytes" -NoNewLine
     
    			# Now go through the file line by line
    			$reader.BaseStream.Position=0
    			$filesdone = $false
    			$linenumber=0
    			$FileResults=@{}
    			$newest=[datetime]"1/1/1900"
    			$linecount++
    			$firsttick=$elapsedtime.elapsed.TotalSeconds
    			$tick=$firsttick+$refreshrate
    			$LastLineLength=1
     
    			try {
    				do {
    					$line = $reader.ReadLine()
    					$linenumber++
    					if (($line -eq "-------------------------------------------------------------------------------" -and $linenumber -gt 16)  ) { 
    						# line is end of job
    						$filesdone=$true
    					} elseif ($linenumber -gt 16 -and $line -gt "" ) {
    						$buckets=$line.split($tab)
     
    						# this test will pass if the line is a file, fail if a directory
    						if ( $buckets.count -gt 3 ) {
    							$status=$buckets[1].trim()
    							$FileResults["$status"]++
     
    							$SizeDateTime=$buckets[3].trim()
    							if ($sizedatetime.length -gt 19 ) {
    								$DateTime = $sizedatetime.substring($sizedatetime.length -19)
    								if ( $DateTime -as [DateTime] ){
    									$DateTimeValue=[datetime]$DateTime
    									if ( $DateTimeValue -gt $newest ) { $newest = $DateTimeValue }
    								}
    							}
    						}
    					}
     
    					if ( $elapsedtime.elapsed.TotalSeconds -gt $tick ) {
    						$line=$line.Trim()
    						if ( $line.Length -gt 48 ) {
    							$line="[...]"+$line.substring($line.Length-48)
    						}
    						$line="$([char]13)Parsing > $($linenumber) ($(($reader.BaseStream.Position/$reader.BaseStream.length).tostring("P1"))) - $line"
    						write-host $line.PadRight($LastLineLength) -NoNewLine
    						$LastLineLength = $line.length
    						$tick=$tick+$refreshrate						
    					}
     
    				} until ($filesdone -or $reader.endofstream)
    			}
    			finally {
    				$reader.Close()
    			}
     
    			$line=$($([string][char]13)).padright($lastlinelength)+$([char]13)
    			write-host $line -NoNewLine
    		}
     
    		$writer.Write("`"$file`"")
    		foreach ( $HeaderParam in $HeaderParams.GetEnumerator() | Sort-Object Name ) {
    			$name = "$(removekey $HeaderParam.Name)"
    			if ( $results[$name] ) {
    				$writer.Write(",$($results[$name])")
    			} else {
    				if ( $ErrorFooter ) {
    					#placeholder
    				} elseif ( $HeaderParam.Value -eq "counts" ) {
    					$writer.Write(",,,,,,") 
    				} else {
    					$writer.Write(",") 
    				}
    			}
    		}
     
    		if ( $ErrorFooter ) {
    			$tmp = $($ErrorFooter -join "").substring(20)
    			$tmp=$tmp.substring(0,$tmp.indexof(")")+1)+","+$tmp
    			$writer.write(",,$tmp")
    		} elseif ( $fp ) {
    			$writer.write(",$LineCount,$($newest.ToString('dd/MM/yyyy hh:mm:ss'))")			
    			foreach ( $FileResult in $FileResults.GetEnumerator() ) {
    				$writer.write(",$($FileResult.Name): $($FileResult.Value);")
    			}
    		}
     
    		$writer.WriteLine()
     
    	} else {
    		write-host -foregroundcolor darkgray "$($file.name) is not recognised as a RoboCopy log file"
    	}
    }
     
    write-host "$filecount files scanned in $($elapsedtime.elapsed.tostring()), $($ProcessCounts["Processed"]) complete, $($ProcessCounts["Error"]) have errors, $($ProcessCounts["Incomplete"]) incomplete"
    write-host  "Results written to $($writer.basestream.name)"
    $writer.close()

    Понадобилось результаты выполнения конвертировать в html и отправить по e-mail. Проблема в том, что генерируемый в процессе выполнения csv заполняется по ходу выполнения скрипта и имя его генерируется в формате robocopy-$(get-date -format "dd-MM-yyyy_HH-mm-ss").csv"). Пробовал:

    import-csv $writer | ConvertTo-Html -Head $Style | Out-File E:\Test\slogs\out.html

    html на выходе пустой. 

    Задачи:

    1) Помимо csv нужно сгенерировать html c именем файла аналогичным csv

    2) Отправить этот файл по e-mail (проблема задать имя файла для отправки, оно каждый раз генерируется)

    3) Задать время создания обрабатываемых файлов логов, например обрабатывать файлы только созданные не более 24 назад.

    Нужна помощь. Спасибо.

    28 августа 2014 г. 16:25

Ответы

  • 1) Поправить пару строк
    $name = "$(get-location)\robocopy-$(get-date -format "dd-MM-yyyy_HH-mm-ss")"
    $writer=new-object System.IO.StreamWriter "$name.csv"
    
    После:
    $writer.close()
    import-csv "$name.csv" | ConvertTo-Html -Head $Style | Out-File "$name.html"
    
    2) Отправка
    
    После:
    import-csv "$name.csv" | ConvertTo-Html -Head $Style | Out-File "$name.html"
    send-mailmessage -from "user01@example.com" -to "user02@example.com" -subject "Sending the Attachment" -body "Forgot to send the attachment." -Attachments "$name.html" -smtpServer smtp.fabrikam.com
    
    
    3) Логи по времени
    $files= get-childitem $SourcePath | Where {$_.CreationTime -ge (Get-Date).AddDays(-1)}

    • Помечено в качестве ответа Evelrin 3 сентября 2014 г. 15:08
    28 августа 2014 г. 17:30
    Отвечающий
  • $style = @"
    <style type="text/css">
    	BODY{font-family: Arial; font-size: 10pt;}
    	TABLE {
    		border: 1px solid black; border-collapse: collapse;
    		white-space: nowrap  
    	} 
    	TH {
    		border: 1px solid black; background: #dddddd; padding: 5px;
    		white-space: nowrap
    	}
    	TD {
    		border: 1px solid black; padding: 5px; 
    		white-space: nowrap
    	}
    </style>
    "@
    
    Import-Csv "$name.csv"  | ConvertTo-Html -Head $style | Foreach {
    	if($_ -match "^<tr>") {
    		if(($_ -split("<td>"))[17] -ne "0</td>") {
    			$_ -replace "^<tr",'<tr bgcolor="red"'
    		}
    		else {
    			$_ 
    		}
    	}
    	else {
    		$_
    	}
    } | Out-File "$name.html"

    Вывод:

    • Помечено в качестве ответа Evelrin 3 сентября 2014 г. 15:08
    2 сентября 2014 г. 19:47
    Отвечающий

Все ответы

  • 1) Поправить пару строк
    $name = "$(get-location)\robocopy-$(get-date -format "dd-MM-yyyy_HH-mm-ss")"
    $writer=new-object System.IO.StreamWriter "$name.csv"
    
    После:
    $writer.close()
    import-csv "$name.csv" | ConvertTo-Html -Head $Style | Out-File "$name.html"
    
    2) Отправка
    
    После:
    import-csv "$name.csv" | ConvertTo-Html -Head $Style | Out-File "$name.html"
    send-mailmessage -from "user01@example.com" -to "user02@example.com" -subject "Sending the Attachment" -body "Forgot to send the attachment." -Attachments "$name.html" -smtpServer smtp.fabrikam.com
    
    
    3) Логи по времени
    $files= get-childitem $SourcePath | Where {$_.CreationTime -ge (Get-Date).AddDays(-1)}

    • Помечено в качестве ответа Evelrin 3 сентября 2014 г. 15:08
    28 августа 2014 г. 17:30
    Отвечающий
  • Тестирую....

    Касаемо e-mail скорее всего нужна будет smtp авторизация, а в некоторых случаях еще и ssl, вот решение:

    $SMTPServer = "smtp.gmail.com"
    $SMTPPort = "587"
    $Username = "username@gmail.com"
    $Password = ""

    $to = "user1@domain.com"
    $cc = "user2@domain.com"
    $subject = "Email Subject"
    $body = "Insert body text here"
    $attachment = "C:\test.txt"

    $message = New-Object System.Net.Mail.MailMessage
    $message.subject = $subject
    $message.body = $body
    $message.to.add($to)
    $message.cc.add($cc)
    $message.from = $username
    $message.attachments.add($attachment)

    $smtp = New-Object System.Net.Mail.SmtpClient($SMTPServer, $SMTPPort);
    $smtp.EnableSSL = $true
    $smtp.Credentials = New-Object System.Net.NetworkCredential($Username, $Password);
    $smtp.send($message)
    write-host "Mail Sent"

    28 августа 2014 г. 19:48
  • Командлет Send-MailMessage поддерживает эти параметры:

    Send-MailMessage -UseSsl -Credential -Port

    29 августа 2014 г. 4:18
    Отвечающий
  • Протестировал, базовый функционал работает. Вопрос по поводу формата html, на данный момент итоговый файл получается корявым. Поддерживает ли Convert-to-html форматирование файла (хотелось бы добиться ровных столбцов, и закрасить поля с ошибками красным)?
    2 сентября 2014 г. 16:42
  • Для начала приложить файл "$name.csv",где есть поля с ошибками.
    2 сентября 2014 г. 17:03
    Отвечающий
  • То, что хотелось бы получить в результате:

    1) Представить в виде таблице, то есть разделить столбцы и строки линиями

    2) Сделать более читабельный вид, слегка раздвинуть столбцы, чтобы было небольшое расстояние между содержимым

    3) Во всех столбцах, кроме Options добиться, чтобы содержимое умещалось в одну строку

    4) Заголовки всех столбцов должны умещаться в одну строку

    5) Если значение в столбце Files Failed отлично от нуля, для все строки установить красный фон (заливку)

    По ссылке csv и html с добавленной вручную ошибкой, а так же один из лог файлов. Добавить в него ошибку можно так же вручную (вызвать ошибку копирования искусственно не вышло).

    https://drive.google.com/file/d/0B1G0u3fKZCelX2pWX1lxQW41cGM/edit?usp=sharing

    2 сентября 2014 г. 18:37
  • $style = @"
    <style type="text/css">
    	BODY{font-family: Arial; font-size: 10pt;}
    	TABLE {
    		border: 1px solid black; border-collapse: collapse;
    		white-space: nowrap  
    	} 
    	TH {
    		border: 1px solid black; background: #dddddd; padding: 5px;
    		white-space: nowrap
    	}
    	TD {
    		border: 1px solid black; padding: 5px; 
    		white-space: nowrap
    	}
    </style>
    "@
    
    Import-Csv "$name.csv"  | ConvertTo-Html -Head $style | Foreach {
    	if($_ -match "^<tr>") {
    		if(($_ -split("<td>"))[17] -ne "0</td>") {
    			$_ -replace "^<tr",'<tr bgcolor="red"'
    		}
    		else {
    			$_ 
    		}
    	}
    	else {
    		$_
    	}
    } | Out-File "$name.html"

    Вывод:

    • Помечено в качестве ответа Evelrin 3 сентября 2014 г. 15:08
    2 сентября 2014 г. 19:47
    Отвечающий
  • Вопрос, для чего нужен второй else? 

    Import-Csv "$name.csv"  | ConvertTo-Html -Head $style | Foreach {
    	if($_ -match "^<tr>") {
    		if(($_ -split("<td>"))[17] -ne "0</td>") {
    			$_ -replace "^<tr",'<tr bgcolor="red"'
    		}
    		else {
    			$_ 
    		}
    	}
    	else {
    		$_
    	}
    } | Out-File "$name.html"

    3 сентября 2014 г. 14:25
  • Если строка не подпадает под условие -match "^<tr>", то добавить ее в вывод. Если уберем второй else, то в файле будут строки, которые начинаются с тэга <tr>.
    3 сентября 2014 г. 14:38
    Отвечающий
  • Добавил в окончательный вариант обработку еще одного столбца:

    if(($_ -split("<td>"))[17] -ne "0</td>" -or ($_ -split("<td>"))[11] -ne "0</td>")

    Все работает, спасибо Вам огромное за помощь!

    Kazun, скрипт предназначен для парсинга логов ROBOCOPY, и возможно будет полезен еще кому нибудь. Если сможете опубликуйте его на своем ресурсе, тут я не удачно указал тему.

    • Изменено Evelrin 3 сентября 2014 г. 15:13
    3 сентября 2014 г. 15:07
  • Оформите скрипт и опубликуйте на сайте http://gallery.technet.microsoft.com/ .
    3 сентября 2014 г. 15:15
    Отвечающий