Read blob zip file directly without writing it to disk - powershell

I'm using ODP.net with Powershell to get blob zipped file.
[void][System.Reflection.Assembly]::LoadFile("C:\DLL\Oracle.ManagedDataAccess.dll")
$OracleConnexion = New-Object Oracle.ManagedDataAccess.Client.OracleConnection('User Id=test;Password="test";Data Source=10.2.2.1/TEST')
$OracleConnexion.Open()
$Query=$OracleConnexion.CreateCommand()
$Query.CommandText="SELECT BLOB from MyTable Where ID=01"
$ExecuteQuery=$Query.ExecuteReader()
$Path = "C:\temp"
while ($ExecuteQuery.Read()){
$Localfile = New-Object IO.FileStream("$($Path)\$($ExecuteQuery["LOG_ID"]).zip",[IO.FileMode]::Create)
$Localfile.Write($ExecuteQuery["XML_TRACE"],0,$ExecuteQuery["XML_TRACE"].Length)
$Localfile.Close()
$Zip = [io.compression.zipfile]::OpenRead("$($Path)\$($Executequery["LOG_ID"]).zip")
$Stream = $Zip.Entries.Open()
$Reader = New-Object IO.StreamReader($stream)
$XML = $Reader.ReadToEnd()
$Reader.Close()
$Stream.Close()
$Zip.Dispose()
}
As you can see, first I'm writing file to disk with $Localfile.Write then with [io.compression.zipfile]::OpenRead i'm reading the content of my zipped file.
My code works, but I want to read my blob directly as zip file without writing it to disk, something like this :
while ($ExecuteQuery.Read()){
$Zip = [io.compression.zipfile]::OpenRead($ExecuteQuery["XML_TRACE"]).zip)
$Stream = $Zip.Entries.Open()
$Reader = New-Object IO.StreamReader($stream)
$XML = $Reader.ReadToEnd()
$XML
$Reader.Close()
$Stream.Close()
$Zip.Dispose()
}
EDIT : it works with ionic !
while ($ExecuteRequete.Read()){
$ZipStream = New-Object System.IO.Memorystream
$ZipStream.Write($ExecuteRequete["XML_TRACE"],0,$ExecuteRequete["XML_TRACE"].Length)
$ZipStream.Position = 0
$Zip = [Ionic.Zip.ZipFile]::Read($ZipStream)
$Stream = New-Object IO.MemoryStream
$Zip.Extract($Stream)
$stream.Position = 0
$Reader = New-Object IO.StreamReader($stream)
$XML = $Reader.ReadToEnd()
$Reader.Close()
$Stream.Close()
$ZipStream.Dispose()
$Zip.Dispose()
}

You can't do it with IO.Compression.Zipfile, see https://msdn.microsoft.com/en-us/library/system.io.compression.zipfile_methods(v=vs.110).aspx for all available methods
You could do it with Ionic zip. It can read zip from a stream:
clear
Add-Type -Path "E:\sw\NuGet\Packages\DotNetZip.1.9.7\lib\net20\Ionic.Zip.dll"
$zip = [Ionic.Zip.ZipFile]::Read($stream)
$file = $zip | where-object { $_.FileName -eq "XMLSchema1.xsd"}
$stream = new-object IO.MemoryStream
$file.Extract($stream)
$stream.Position = 0
$reader = New-Object IO.StreamReader($stream)
$text = $reader.ReadToEnd()
$text
$reader.Close()
$stream.Close()
$zip.Dispose()
Here's the doc: http://dotnetzip.herobo.com/DNZHelp/Index.html

Related

Add an image to Word using PowerShell

As stated in the Title, I would like to add an image into a Word document. Though my goal is to NOT use a path (stating where the image is located).
**Like this: **
$word = New-Object -ComObject Word.Application
$word.visible = $true
$document = $word.documents.Add()
$selection = $word.selection
$newInlineShape = $selection.InlineShapes.AddPicture($path)
But rather using some type of Base64String, so that this skript works with every device, regardless if the image path doesn't exist.
My attempt:
$word = New-Object -ComObject Word.Application
$word.visible = $true
$document = $word.documents.Add()
$selection = $word.selection
$base64ImageString = [Convert]::ToBase64String((Get-Content $path -encoding byte))
$imageBytes = [Convert]::FromBase64String($base64ImageString)
$ms = New-Object IO.MemoryStream($imageBytes, 0, $imageBytes.Length)
$ms.Write($imageBytes, 0, $imageBytes.Length);
$alkanelogo = [System.Drawing.Image]::FromStream($ms, $true)
$pictureBox = new-object Windows.Forms.PictureBox
$pictureBox.Width = $alkanelogo.Size.Width;
$pictureBox.Height = $alkanelogo.Size.Height;
$pictureBox.Location = New-Object System.Drawing.Size(153,223)
$pictureBox.Image = $alkanelogo;
$newInlineShape = $selection.InlineShapes.AddPicture($pictureBox.Image)
Note: The variable "$path" is only here as a placeholder
I've figured it out. I downloaded the image to my local computer, converted it to a base64 string and then back to an image.
So that this script works with every user regardless of there path, I built in it to download the file to a specific path (that I created).
Powershell will then extract the image from the path I created.
$filepath = 'C:\temp\image.png'
$folderpath = 'C:\temp\'
if([System.IO.File]::Exists($filepath -or $folderpath)){
rmdir 'C:\temp\image.png'
$b64 = "AAA..."
$bytes = [Convert]::FromBase64String($b64)
[IO.File]::WriteAllBytes($filepath, $bytes)
}else{
mkdir 'C:\temp\' -ErrorAction SilentlyContinue
$b64 = "AAA..."
$bytes = [Convert]::FromBase64String($b64)
[IO.File]::WriteAllBytes($filepath, $bytes)
}

Converting pptx file to pdf using powershell

I am trying to convert .pptx file to .pdf using powershell. I have used below code
write-host "Converting pptx to pdf....." -ForegroundColor Green
$ppt = New-Object -com powerpoint.application
$opt = [Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType]::ppSaveAsPDF
$ifile = $file.FullName
$pres = $ppt.Presentations.Open($ifile)
$pathname = split-path $ifile $filename = Split-Path $ifile -Leaf
$file = $filename.split(".")[0]
$ofile = $pathname + "\" + $file + ".pdf"
$pres.SaveAs($ofile, $opt)
While running the code, i am getting error as:
Error HRESULT E_FAIL has been returned from a call to a COM component.
Any help would be highly appreciated
$pptx = "C:\test\test.pptx"
$pdf = "C:\test\test.pdf"
$ppt = New-Object -ComObject PowerPoint.Application
$ppt.Visible = $True
$presentation = $ppt.Presentations.Open($pptx)
$presentation.SaveAs($pdf, [Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType]::ppSaveAsPDF)
$presentation.Close()
$ppt.Quit()

Powershell unzip stream

Is there a built-in cmdlet or some composition thereof that would allow me to start unzipping a file stream as each chunk is downloaded? I have a PowerShell script that needs to download a large (10 GB) file, and I have to wait until it is done right now before it starts expanding...
$wc = New-Object net.webclient
$wc.Downloadfile($appDataSnapshotUri, "%DataSnapshotFileName%.zip") # this can take some time
Expand-Archive -Path "%DataSnapshotFileName%.zip" -DestinationPath Run # so can this
OK, turns out zip file doesn't need to be fully downloaded to be decompressed, you can compress/decompress streams. There is some built in capabilities in .Net for stream compression, but it will not work with zip archives. You can use SharpZipLib library for that:
Download .nupckg from https://www.nuget.org/packages/SharpZipLib/
Extract files to any folder. You'll need ICSharpCode.SharpZipLib.dll from lib/net45
Below is my simplified translation of their example:
https://github.com/icsharpcode/SharpZipLib/wiki/Zip-Samples#unpack-a-zip-using-zipinputstream-eg-for-unseekable-input-streams
Add-Type -Path ".\ICSharpCode.SharpZipLib.dll"
$outFolder = ".\unzip"
$wc = [System.Net.WebClient]::new()
$zipStream = $wc.OpenRead("http://gitlab/test/test1/raw/master/sample.zip")
$zipInputStream = [ICSharpCode.SharpZipLib.Zip.ZipInputStream]::New($zipStream)
$zipEntry = $zipInputStream.GetNextEntry()
$fileName = $zipEntry.Name
$buffer = New-Object byte[] 4096
$sw = [System.IO.File]::Create("$outFolder\$fileName")
[ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($zipInputStream, $sw, $buffer)
$sw.Close()
It will only extract first entry, you can add a while loop it this sample works.
Here is a snippet with while loop to extract multiple files (put it after $zipEntry = $zipInputStream.GetNextEntry() on the example above):
While($zipEntry) {
$fileName = $zipEntry.Name
Write-Host $fileName
$buffer = New-Object byte[] 4096
$sw = [System.IO.File]::Create("$outFolder\$fileName")
[ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($zipInputStream, $sw, $buffer)
$sw.Close()
$zipEntry = $zipInputStream.GetNextEntry()
}
Edit
Here is what I found to work...
Add-Type -Path ".\ICSharpCode.SharpZipLib.dll"
$outFolder = "unzip"
$wc = [System.Net.WebClient]::new()
$zipStream = $wc.OpenRead("https://github.com/Esri/file-geodatabase-api/raw/master/FileGDB_API_1.5/FileGDB_API_1_5_VS2015.zip")
$zipInputStream = [ICSharpCode.SharpZipLib.Zip.ZipInputStream]::New($zipStream)
$zipEntry = $zipInputStream.GetNextEntry()
while($zipEntry) {
if (-Not($zipEntry.IsDirectory)) {
$fileName = $zipEntry.Name
$buffer = New-Object byte[] 4096
$filePath = "$pwd\$outFolder\$fileName"
$parentPath = "$filePath\.."
Write-Host $parentPath
if (-Not (Test-Path $parentPath)) {
New-Item -ItemType Directory $parentPath
}
$sw = [System.IO.File]::Create("$pwd\$outFolder\$fileName")
[ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($zipInputStream, $sw, $buffer)
$sw.Close()
}
$zipEntry = $zipInputStream.GetNextEntry()
}
To expand on Mike Twc's answer, a script to do it with and without stream, and compare how long it takes:
$url = "yoururlhere"
function UnzipStream () {
Write-Host "unzipping via stream"
$stopwatch1 = [system.diagnostics.stopwatch]::StartNew()
Add-Type -Path ".\ICSharpCode.SharpZipLib.dll"
$outFolder = "unzip-stream"
$wc = [System.Net.WebClient]::new()
$zipStream = $wc.OpenRead($url)
$zipInputStream = [ICSharpCode.SharpZipLib.Zip.ZipInputStream]::New($zipStream)
$zipEntry = $zipInputStream.GetNextEntry()
while($zipEntry) {
if (-Not($zipEntry.IsDirectory)) {
$fileName = $zipEntry.Name
$buffer = New-Object byte[] 4096
$filePath = "$pwd\$outFolder\$fileName"
$parentPath = "$filePath\.."
Write-Host $parentPath
if (-Not (Test-Path $parentPath)) {
New-Item -ItemType Directory $parentPath
}
$sw = [System.IO.File]::Create("$pwd\$outFolder\$fileName")
[ICSharpCode.SharpZipLib.Core.StreamUtils]::Copy($zipInputStream, $sw, $buffer)
$sw.Close()
}
$zipEntry = $zipInputStream.GetNextEntry()
}
$stopwatch1.Stop()
Write-Host "extraction took $($stopWatch1.ElapsedMilliseconds) millis with stream"
}
function UnzipWithoutStream() {
Write-Host "Extracting without stream"
$stopwatch2 = [system.diagnostics.stopwatch]::StartNew()
$outFolder2 = "unzip-normal"
$wc2 = New-Object System.Net.WebClient
$wc2.DownloadFile($url, "$pwd\download.zip")
$of2 = New-Item -ItemType Directory $outFolder2
Expand-Archive -Path "download.zip" -DestinationPath $of2.FullName
$stopwatch2.Stop()
Write-Host "extraction took $($stopWatch2.ElapsedMilliseconds) millis without stream"
}
UnzipStream
UnzipWithoutStream

folders downloading as files in ftp

So in my powershell script when it starts up it polls a ftp server and downloads any files that aren't in the local folder. The problem is when it gets to a folders it downloads them as files. this is my code for checking for new files:
$LocFolder = 'C:\EMSDropBox\*'
Remove-Item $LocFolder
$ftprequest = [System.Net.FtpWebRequest]::Create("ftp://NZHQFTP1/tbarnes")
$ftprequest.Proxy = $null
$ftprequest.KeepAlive = $false
$ftprequest.TimeOut = 10000000
$ftprequest.UsePassive = $False
$ftprequest.Credentials = New-Object System.Net.NetworkCredential("tbarnes", "Static_flow2290")
$ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory
$FTPResponse = $ftprequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object System.IO.Streamreader($ResponseStream)
$filename = $FTPReader.ReadLine()
while($filename -ne $null)
{
try
{
if((Test-Path ("C:\emsdropbox\"+$filename)) -ne $true)
{
downloadFtp($filename)
}
$filename = $FTPReader.ReadLine()
}
catch
{
Write-Host $_
}
}
$FTPReader.Close()
$FTPResponse.Close()
$ResponseStream.Close()
and this is the downloadFtp function:
# FTP Config
$FTPHost = "****"
$Username = "******"
$Password = "*********"
$FTPFile = $file
# FTP Log File Url
$FTPFileUrl = "ftp://" + $FTPHost + "/tbarnes/" + $FTPFile
# Create FTP Connection
$FTPRequest = [System.Net.FtpWebRequest]::Create("$FTPFileUrl")
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($Username, $Password)
$FTPRequest.Method = [System.Net.WebRequestMethods+Ftp]::DownloadFile
$FTPRequest.UsePassive = $false
$FTPRequest.UseBinary = $true
$FTPRequest.KeepAlive = $false
$targetfile = New-Object IO.FileStream (("C:\emsdropbox\"+$file),[IO.FileMode]::Create)
# Get FTP File
$FTPResponse = $FTPRequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
$FTPReader = New-Object -typename System.IO.StreamReader -ArgumentList $ResponseStream
[byte[]]$readbuffer = New-Object byte[] 1024
#loop through the download stream and send the data to the target file
do{
$readlength = $ResponseStream.Read($readbuffer,0,1024)
$targetfile.Write($readbuffer,0,$readlength)
}
while ($readlength -ne 0)
$FTPReader.Close()
Im not sure why it wont pull them down as folders so any help or pointers would be great!
The FTP methods don't support downloading of folders, or recursion, by themselves, so there's no other way I can think of doing this but what I've suggested below.
Change the method so you can differentiate between files and directories and handle them accordingly.
Change $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectory to $ftprequest.Method = [System.Net.WebRequestMethods+Ftp]::ListDirectoryDetails
Which will list in this format:
-rwxrwxrwx 1 owner group 277632 Mar 4 17:15 xml_socket.log.rar
Directories will have a d in place of the - at the start of the line.
Here is an amended try block that will match only files so you can pass to the downloadFtp function:
try
{
if(!($filename -match '^d')){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
downloadFtp(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
If you want to then get a list of directories, use the following try block against the same ftpresponse stream and for each, ListDirectoryDetails to get the list of files in each directory to process them:
try
{
if($filename -match '^d'){if((Test-Path ("C:\emsdropbox\"+$filename.Split(" ")[8])) -ne $true)
{
listdir(($filename -split '\s+')[8])
}}
$filename = $FTPReader.ReadLine()
}
You may also have to create the local directories too which you can do in powershell as follows:
New-Item c:\emsdropbox\newdir -type directory

Document manipluation: powershell

I am attempting to take a large document, search for a "^m" (page break) and create a new text file for each page break I find.
Using:
$SearchText = "^m"
$word = new-object -ComObject "word.application"
$path = "C:\Users\me\Documents\Test.doc"
$doc = $word.documents.open("$path")
$doc.content.find.execute("$SearchText")
I am able to find text, but how do I save the text before the page break into a new file? In VBScript, I would just do a readline and save it to a buffer, but powershell is much different.
EDIT:
$text = $word.Selection.MoveUntil (cset:="^m")
returns an error:
Missing ')' in method call.
I think my solution is kinda stupid, but here is my own solution (please help me find a better one):
Param(
[string]$file
)
#$file = "C:\scripts\docSplit\test.docx"
$word = New-Object -ComObject "word.application"
$doc=$word.documents.open($file)
$txtPageBreak = "<!--PAGE BREAK--!>"
$fileInfo = Get-ChildItem $file
$folder = $fileInfo.directoryName
$fileName = $fileInfo.name
$newFileName = $fileName.replace(".", "")
#$findtext = "^m"
#$replaceText = $txtPageBreak
function Replace-Word ([string]$Document,[string]$FindText,[string]$ReplaceText) {
#Variables used to Match And Replace
$ReplaceAll = 2
$FindContinue = 1
$MatchCase = $False
$MatchWholeWord = $True
$MatchWildcards = $False
$MatchSoundsLike = $False
$MatchAllWordForms = $False
$Forward = $True
$Wrap = $FindContinue
$Format = $False
$Selection = $Word.Selection
$Selection.Find.Execute(
$FindText,
$MatchCase,
$MatchWholeWord,
$MatchWildcards,
$MatchSoundsLike,
$MatchAllWordForms,
$Forward,
$Wrap,
$Format,
$ReplaceText,
$ReplaceAll
)
$newFileName = "$folder\$newFileName.txt"
$Doc.saveAs([ref]"$newFileName",[ref]2)
$doc.close()
}
Replace-Word($file, "^m", $txtPageBreak)
$word.quit()
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($word)
Remove-Variable word
#begin txt file manipulation
#add end of file marker
$eof = "`n<!--END OF FILE!-->"
Add-Content $newfileName $eof
$masterTextFile = Get-Content $newFileName
$buffer = ""
foreach($line in $masterTextFile){
if($line.compareto($eof) -eq 0){
#end of file, save buffer to new file, be done
}
else {
$found = $line.CompareTo($txtPageBreak)
if ($found -eq 1) {
$buffer = "$buffer $line `n"
}
else {
#save the buffer to a new file (still have to write this part)
}
}
}