Powershell GUI Drag-and-Drop empty arg from email attachments - powershell

I have a GUI with a read-only textbox that I drag files to and a function then runs with the file. This works on my machine for files in local download folders, all file extensions, outlook email attachments, etc. I have no issues.
A colleague has begun using it and he is unable to successfully drag email attachments from outlook into the box. The arg is empty. It all works on my machine, but not his. I had one other person test, and they had the same issue.
$filebox.Add_DragEnter({
if ($_.Data.GetDataPresent([Windows.Forms.DataFormats]::FileDrop)) {
$_.Effect = [Windows.Forms.DragDropEffects]::Copy
}
})
$filebox.Add_DragDrop({
foreach ($file in $_.Data.GetData([Windows.Forms.DataFormats]::FileDrop)) {
create_dir $file # Function doesn't even trigger here with outlook email attachments because no arg
}
})
We are all pulling from a shared inbox. Nothing is hard-coded to identify the user, or define a set path, it should just grab the path of the arg dropped in the box, right?
I'm just confused how the drag/drop is working for some files but not email attachments, and then also working fully on my machine.
I would consider myself a pretty novice coder, and first time ever messing with Powershell. (I do not have access to anything else in this situation. No ability to compile c#, powershell is it.). Maybe I'm missing something fundemental here. Anyone have any thoughts on this?

File format wont be present since there is no physical file on the file system. see Upload fails when user drags and drops attachment from email client

$filebox.Add_DragDrop({
if ($_.Data.GetDataPresent([System.Windows.Forms.DataFormats]::FileDrop)) {
foreach ($file in $_.Data.GetData([System.Windows.Forms.DataFormats]::FileDrop)) {
create_dir $file
}
}
else {
$outlook = New-Object -ComObject Outlook.Application
$s = $outlook.ActiveExplorer().Selection
foreach ($item in $s){
foreach($a in $item.Attachments){
$name = Join-Path -Path $dlpath -ChildPath $a.filename
$a.SaveAsFile($name)
create_dir $name
}
}
}
})
As Dmitry noted, the files aren't actually saved on the machine so I can't just Copy-Item. Here, the file is saved, and then the create_dir function is called and it can copy the item from the new download path.

Related

Collecting user input with pop up window in Powershell

so I am trying to prepare a simple robocopy script. the script will be used by almost 500 users, so I am trying to keep it as simple and as user-friendly as possible.
to collect the information such as source and destination, i wanted to have a pop up window asking users to enter the information. I checked on the other forums and here as well, so far i found several alternatives, unfortunately none of them "does the trick"
Option 1 (my favorite except PowerShell hangs when I use it):
Add-Type -AssemblyName Microsoft.VisualBasic
$title = 'Your Current File Shares (Source)'
$msg = 'Please enter the EEX file share you want to copy from ( please make sure the format is \\server\share\...) :'
$source = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
Add-Type -AssemblyName Microsoft.VisualBasic
$title = 'Your new drive (Destination)'
$msg = 'Please enter to where you want to copy your files (please make sure you choose the full destination) :'
$destination = [Microsoft.VisualBasic.Interaction]::InputBox($msg, $title)
Robocopy $source $Destination /log:N:\logfile.txt
Option 2 (similar to option just a different way to call VB it seems): replying the first line Add-Type -AssemblyName Microsoft.VisualBasic with [void][Reflection.Assembly]::LoadWithPartialName('Microsoft.VisualBasic') , doesn't make it any better .
with both 2 options above PowerShell hangs after the 3rd run. SOmetimes after the first run it hangs. Odd thing is it runs succesfully and runs the robocopy command , but then after it's done it stops responding after a few minutes. Not sure if VB is looping in the background and has to be stopped?
Option 3:
$source = Read-Host 'Enter Your current file share:' -AsSecureString
$destination = Read-Host 'Enter Your new file share:' -AsSecureString
This is simple enough, however you cannot control anything in the box, which is something we can live with, but the main issue is the secure string, so it doesn't allow the user to see what he or she is typing, which would lead to a lot of human errors.
Option 4:
function copy_files {
param (
[string]$Copy_from,
[string]$Copy_to
)
[pscustomobject]#{
copy_from = $Copy_from
copy_to = $copy_to
}
}
$result = Invoke-Expression (Show-Command Copy_files -PassThru )
$result
has a form that is really not desirable , with the "copy" in the middle on the bottom (on the bottom of the form one sees ok / copy / cancel ) which would confuse users causing to hit copy and wait for something to happen (as the purpose is to transfer files…)
another negative thing is that it is very limited in terms what text and title you can use (or at least what I can use as I tried to have spaces but it wouldn't recognize it no matter if put them in quotations or double quotations). But such cosmetic con i can live with.
I cannot add any additional modules so showui for example is unfortunately not an option.
Any ideas on how this can be done?
Thanks in advance
There are pre-built scripts for this sort of thing that you can use as-is or tweak as needed.
The AutoCopier - PowerShell File Copy Utility w/GUI
A PowerShell GUI utility to copy files to computers based on a supplied text file of Hostnames/IP addresses. Please read below for more details and feedback would be appreciated!
Download: AutoCopier.ps1

Jenkins + PowerShell - Converting Zip File to Bytes, Transfer Bytes, Then Reconstruct Bytes to Zip

As this complex problem now has a viable solution, I wanted to update this to accommodate the solution so that others can benefit from this.
Problem
I am working with Jenkins and PowerShell to perform a series of actions. One such action is to gather up and consolidate all associate log files into a single folder named accordingly to associate it with the originating machine/server, zip the folder up, transfer it back to the Jenkins workspace environment somehow, store the zips into a single master folder, and convert said master folder into an artifact which can be downloaded at a later time/date for debugging. However, since this action tends to involve the two-hop exchange, I have hit a number of walls that have inhibited my ability to perform simple file transfers. So I am turning to the community to seek assistance from those who hopefully have more experience doing this kind of thing.
The only way (that I have found so far) for me to perform many of the actions I need is to connect to the target machine through an Invoke-Command ; however, it did not take long for me to start running into walls. One such wall being the issues with file transfer between Jenkins and the target machine. I have found that by creating an object that equals the invoked command ( ex: $returnMe = Invoke-Command {} ), I am able to return objects from the action to be stored in the variable. This has given me a possible resolution to the problem of returning an item to Jenkins through the session... but now brings forward the question:
Question
Using PowerShell, is it possible to zip a folder up, then convert
that zip file into an object to be passed, and then reconstruct that
zip file using the contents of the object? If so, how?
Resolution:
A special shout out to #ArcSet for assistance in getting this to work right.
Alright, so it is extremely possible to do this; however, it takes a series of key steps to make it happen. One such thing that this is a four to five step process:
1) Gather the files into the target folder and zip the folder
2) Convert the zip file into bytes that can be easily transfered
3) Pass those byte through a special custom object
4) Parse the object to ensure excess bytes did not find their way into the file.
5) Convert the bytes back into a zip file on Jenkins
Below is the code (with comments) that shows how this was achieved:
# Parse Through Each Server One By One
ForEach ($server in $servers) {
# Create a Variable to Take in the Invoke-Command and Receive what is Returned
$packet = Invoke-Command -ComputerName $server -ArgumentList $server -Credential $creds -ScriptBlock {
# Function for Creating a Zip File
Function Create-ZipFromFolder ([string]$Source, [string]$SaveAs) {
Add-Type -Assembly "System.IO.Compression.FileSystem"
[IO.Compression.ZipFile]::CreateFromDirectory($Source, $SaveAs)
}
# Function for Converting Zip to Bytes For File Transfer
Function Get-BytesFromFile($Path) {
return [System.IO.File]::ReadAllBytes($Path)
}
# Absorb and Maske Server IP for the Server to Use For Logging Purposes and FileNaming
$maskedAddress = $args[0] -Replace ('.+(?=\.\d+$)', 'XX.XX.XX')
# Construct the Path For Consolidated Log Files
$masterLogFolderPath = ("C:\Logs\RemoteLogs\$maskedAddress")
# Series of Code to Create Log Folder, Consolidate Files, And Delete Unnecessary Data For Cleanup
# Establish what to Save the Zip As. You Will Want The Path Included to Prevent it From Finding Path of Least Resistance Upon Saving
$zipFile = ($masterLogFolderPath + ".zip")
Try {
# Here is Where We Call Our Compression Function
Create-ZipFromFolder -Source $masterLogFolderPath -SaveAs ZipFile
# Next We Convert the Zip to Bytes
[byte[]]$FileAsBytes = Get-BytesFromFile -Path $zipFile
}
Catch {
Write-Error $_.Exception.Message
}
# Now We Return the New Object to Jenkins
return New-Object -TypeName PSCustomObject -Property #{Response=$FileAsBytes}
}
# Function to Convert the Bytes Back Into a Zip File
Function Create-FileFromBytes([byte[]]$Bytes, [string]$SaveAs) {
# It was Discovered that Depending Upon the Environment, Extra Bytes Were Sometimes Added
# These Next Lines Will Help to Remove Those
For (($k = 0), ($kill = 0); $kill -lt 1; $k++) {
If ($Bytes[$k] -gt 0) {
$kill = 1
# Truncate the Excess Bytes
$newByteArray = ($Bytes)[$k..(($Bytes).Length -1)]
}
}
# Reconstruct the Zip File
[System.IO.File]::WriteAllBytes($SaveAs, $NewByteArray)
}
Try {
# Call the Function to Begin Reconstruction
Create-FileFromBytes -SaveAs "$env:Workspace\MasterLog\$maskedAddress.zip" -Bytes $packet.response
}
Catch {
Write-Error $_.Exception.Message
}
Now this is just a base skeleton without all the heavy stuff, but these are the key pieces that put everything together. For the most part, following this formula will return the desired results. I hope this helps others who find themselves in a similar situation.
Thank you all again for your assistance
So first you will need to create a zip file.
You will need to load the assembly system first by using add-type.
Use the CreateFromDirectory method to build and save the zip file.
Use the System.IO.File class to read all the bytes from the zip. At this point you can send this data to another computer. Finally use the System.IO.File class to turn the byte array back into a file
function Create-ZipFromFrolder([string]$Source,[string]$SaveAs){
Add-Type -assembly "system.io.compression.filesystem"
[io.compression.zipfile]::CreateFromDirectory($Source, $SaveAs)
return $SaveAs
}
function Get-BytesFromFile([string]$File){
return [byte[]]$ZipFileBytes = [System.IO.File]::ReadAllBytes($File)
}
function Create-FileFromBytes([byte[]]$Bytes, [string]$SaveAs){
return [System.IO.File]::WriteAllBytes($SaveAs, $Bytes)
}
[byte[]]$FileAsBytes = Get-BytesFromFile -File (Create-ZipFromFrolder -Source "C:\ZipFolder" -SaveAs "C:\Test\test.zip")
Create-FileFromBytes -SaveAs "C:\ZipFolder\Test.zip" -Bytes $FileAsBytes

Powershell folder creation

I have a powershell script that creates folders on our NAS for each student according to their student numbers. The names for the folders comes from a .csv file that I import from the server. This is what I have got:
Set-Location "C:\studentdata"
$StudFolders = import-csv \\servername\datafolder\studfolders.csv
ForEach ($StudFolders in $StudFolders) {
if(Test-Path -Path C:\Studentdata\$StudFolders) {
New-Item $StudFolders.Name -type directory
}else{
"Folders already created"
}
}
This script works great, if I run it only once. If I run it again, I get errors in the console window about the folders already existing. What I want to do is catch the errors with the IF part of the script, but I am not sure if I have the correct usage of the IF for powershell. This will help if I edit the .csv with more student number it will display without errors.
Can someone point me in the right direction?
EDIT:
This is what I have in the studfolders.csv
Name
2003040052
2003060213
2003060310
2003060467
Lets take a look at your logic:
you are using an if statement, which works if something returns true
you are using a test-path cmdlet which returns true if something exists
See the problem? you need to do it vice versa:
if (!(test-path ...)) { ... } # ! - is the operator to invert true to false
or you can switch if and else content, so when if executes it returns "Folders already created", and else creates folders
As a matter of coding style, I would avoid using the same variable name with different semantics, as in ($Studfolders in $Studfolders). Code like this is very hard to read a few months down the road. I generally use the singular for each object in the collection, as in ($Studfolder in $Studfolders). But if your style works for you, OK.
Previous answers have already pointed out that your logic is backwards. You need to reverse it.
Next, it doesn't look to me as though you are accessing the component of each item in the loop. When you do an Import-Csv, several things happen: The first record in the csv file is treated as a header, providing the names for the fields that follow. If there is indeed a header in your csv file, you need to reference it when you retrieve the first field from each item, even if it's the only field.
The result of an import-csv is an array of custom objects. Each custom object looks like a hashtable that contains key, value pairs. Something like this might work
Set-Location "C:\studentdata"
$StudFolders = import-csv \\servername\datafolder\studfolders.csv
ForEach ($StudFolder in $StudFolders) {
if(Test-Path -Path C:\Studentdata\$StudFolder.Name) {
"Folders already created"
}else{
New-Item $StudFolder.Name -type directory
}
}
I have presumed that the first record in the Csv file looks like this:
"Name"
That is why I referenced the field as $Studfolder.Name"
If this isn't the case, you are going to have to do something different.
Having tried everything with the script, I could not get it to catch the errors. I decided to ask my manager, and he came up with the following solution that works quite well and catches the errors.
import-csv '\\servername\datafolder\studfolders.csv'|ForEach-Object -process {
$path =$_.Name
$path='C:\Studentdata\'+$path
if (Test-Path -Path $path){
"Folder already created"
} else {
New-Item $path -type directory
}
}
Thanks to #Walter Mitty and #4c74356b41 for help try to find an answer.

iTextSharp to merge PDF files in PowerShell

I have a folder which contains thousands of PDF files. I need to filter through these files based on file name (which will group these into 2 or more PDF's) and then merge these 2 more more PDF's into 1 PDF.
I'm OK with group the files but not sure the best way of then merging these into 1 PDF. I have researched iTextSharp but have been unable to get this to work in PowerShell.
Is iTextSharp the best way of doing this? Any help with the code for this would be much appreciated.
Many thanks
Paul
Have seen a few of these PowerShell-tagged questions that are also tagged with itextsharp, and always wondered why answers are given in .NET, which can be very confusing unless the person asking the question is proficient in PowerShell to begin with. Anyway, here's a simple working PowerShell script to get you started:
$workingDirectory = Split-Path -Parent $MyInvocation.MyCommand.Path;
$pdfs = ls $workingDirectory -recurse | where {-not $_.PSIsContainer -and $_.Extension -imatch "^\.pdf$"};
[void] [System.Reflection.Assembly]::LoadFrom(
[System.IO.Path]::Combine($workingDirectory, 'itextsharp.dll')
);
$output = [System.IO.Path]::Combine($workingDirectory, 'output.pdf');
$fileStream = New-Object System.IO.FileStream($output, [System.IO.FileMode]::OpenOrCreate);
$document = New-Object iTextSharp.text.Document;
$pdfCopy = New-Object iTextSharp.text.pdf.PdfCopy($document, $fileStream);
$document.Open();
foreach ($pdf in $pdfs) {
$reader = New-Object iTextSharp.text.pdf.PdfReader($pdf.FullName);
$pdfCopy.AddDocument($reader);
$reader.Dispose();
}
$pdfCopy.Dispose();
$document.Dispose();
$fileStream.Dispose();
To test:
Create an empty directory.
Copy code above into a Powershell script file in the directory.
Copy the itextsharp.dll to the directory.
Put some PDF files in the directory.
Not sure how you intend to group filter the PDFs based on file name, or if that's your intention (couldn't tell if you meant just pick out PDFs by extension), but that shouldn't be too hard to add.
Good luck. :)

Working with Word templates with Powershell

I am writing a function that is part of a much larger script that will take input from a web form, check to see if that user exists in either our AD or Linux systems, create the account if it doesn't, email the user when it's done, then create a Word document that we can print out and give them with their credentials (sans temp password), email address, and basic information about our IT services. I have been beating my head against the wall with the Word integration. There is almost ZERO Powershell documentation online for Word integration. I've been having to translate what I can from C# and VB and even half of that isn't even translateable. I've got it mostly working now but I'm having problems getting PS to put my text in the correct location in the Word template. I have a Word Template with 4 bookmarks where I am inserting the user's name, username, email address, and account expiration. The problem is, PS is placing all of the text at the same bookmark. I've found that if I put info in the script statically it will work (ie. $FillName.Text = 'John Doe') but if I use a variable it will just stick all of them at the first bookmark. Here is my code:
Function createWordDocument($fullname,$sam,$mailaddress,$Expiration)
{
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.add("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome2.dotx")
$FillName=$doc.Bookmarks.Item("Name").Range
$FillName.Text="$fullname "
$FillUser=$doc.Bookmarks.Item("Username").Range
$FillUser.Text="$sam"
$FillMail=$doc.Bookmarks.Item("Email").Range
$FillMail.Text="$mailaddress"
$FillExpiration=$doc.Bookmarks.Item("Expiration").Range
$FillExpiration.Text="$Expiration"
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\test1.docx"
$doc.SaveAs([ref]$file)
$Word.Quit()
}
The function is receiving parameters that originated from a import-csv. $fullname, $sam and potentially $mailaddress have all been modified from their original inputs. #Expiration comes from the import-csv raw. Any help would be appreciated. This seems to be the most relevant info I could find and as far as I can tell I've got the same code, but It won't work for multiple bookmarks.
Ok, like I suggested you can setup a Mail Merge base that you can use to create docs for people. It does mean that you would need to output your data to a CSV file, but that is pretty trivial.
Start by setting up a test CSV with the data that you want to include. For simplicity you may want to place it with the word doc that references it. We'll call it mailmerge.csv for now, but you can name it whatever you want. Looks like Name, UserName, Email, and Expiration are the fields you would want. You can use dummy data in those fields for the time being.
Then setup your mail merge in Word, and save it someplace. We'll call it Welcome3.docx, and stash it in the same place as your last doc. Then, once it's setup to reference your CSV file, and saved, you can launch Word, open the master document, and perform the merge, then just save the file, and away you go.
I'll just use a modified version of your function which will create the CSV from the parameters provided, open the merge doc, execute the merge, save the new file, and close word. Then it'll pass a FileInfo object back so you can use that to send the email, or whatever.
Function createWordDocument($fullname,$sam,$mailaddress,$Expiration)
{
[PSCustomObject]#{Name=$fullname;Username=$sam;Email=$mailaddress;Expiration=$Expiration}|Export-Csv "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\mailmerge.csv" -NoTypeInformation -Force
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.Open("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome3.dotx")
$doc.MailMerge.Execute()
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\$fullname.docx"
($word.documents | ?{$_.Name -Match "Letters1"}).SaveAs([ref]$file)
$Word.Quit()
[System.IO.FileInfo]$file
}
TheMadTechnician put me on the right track, but I had to do some tweaking. Here is what I wound up with:
Function createWordDocument($fullname)
{
$word = New-Object -ComObject "Word.application"
$doc = $word.Documents.Add("C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\welcome_letter.docx")
$doc.MailMerge.Execute()
$file = "C:\Users\smiths\Documents\Powershell Scripts\webformCreateUsers\$fullname.docx"
($word.documents | ?{$_.Name -Match "Letters1"}).SaveAs([ref]$file)
$quitFormat = [Enum]::Parse([Microsoft.Office.Interop.Word.WdSaveOptions],"wdDoNotSaveChanges")
$Word.Quit([ref]$quitformat)
}
Instead of passing the arguments to the function, I had the main function create the mailmerge.csv file for me and just have the Word template connect to it. I'm still passing $fullname since that's what I'm naming the file in the end. The two major hiccups in the end were that everytime a mailmerge document file is opened, Word asks if you want to conect back to the source data. This means that when Powershell was trying to open it, Word was waiting for interaction and then PS would close it when it thought it was done. Of course, this meant that nothing got done. I found that there is a registry key that you must create to enable Word to skip the SQL Security check. for posterity's sake you must create a key here:
HKCU\Software\Microsoft\Office\14.0\Word\Options\ called SQLSecurityCheck with a DWORD value of 0. That allowed Word to properly open the template and manipulate the files. The last bit of trouble that I had was that Word was wanting to re-save the original file each time it ran and would leave a dialogue box open which would leave Word open and in memory. The last 2 lines force word to close without saving.