Email attachment not saving correctly - powershell

Every morning I have an email that automatically generates, and withing the email is a CSV attachment. This is what I have so far:
$outlook = New-Object -ComObject Outlook.Application
$namespace = $outlook.GetNamespace("MAPI")
# Below: 6 is the default for inbox, so this saves the user from having to
# select the inbox folder. Change if emails w/ attatchements are going to a
# different folder
$folder = $namespace.GetDefaultFolder(6)
$filepath = "I:\PowerShell"
$folder.Items | foreach {
$_.attachments | foreach {
$filename = $_.filename
if ($filename.Contains("example.csv")) {
$_.SaveAsFile((Join-Path $filepath $filename))
Rename-Item -LiteralPath '.\example.csv' -NewName "Server.csv" -Force
}
}
}
When I run this the first time, it successfully grabs the attachment from the email, saves the attachment to the designated folder, and renames it to "Server.csv". However, when I run this a second time, it will grab the attachment and save it, however it will not rename/overwrite it as "Server.csv", so it will only save it as example.csv. It will sometimes throw an error saying
Rename-Item : Cannot create a file when that file already exists
However, I have -Force there, so I'm not sure why that is happening.
Any advice?

-Force allows you to rename a file that is hidden or read-only. It does not allow you to rename a file to replace an existing file. Check if the destination file already exists, and remove it if it does. Then save the attachment directly with the desired filename.
$outfile = Join-Path $filepath 'Server.csv'
if (Test-Path -LiteralPath $outfile) {
Remove-Item $outfile -Force
}
$_.SaveAsFile($outfile)

Related

Powershell: ForEach Copy-Item doesn't rename properly when retrieving data from array

I am pretty new to PowerShell and I need some help. I have a .bat file that I want to copy as many times as there are usernames in my array and then also rename at the same time. This is because the code in the .bat file remains the same, but for it to work on the client PC it has to have the username as a prefix in the filename.
This is the code that I have tried:
$usernames = Import-Csv C:\Users\Admin\Desktop\usernames.csv
$file = Get-ChildItem -Path 'C:\Users\Admin\Desktop\generatedbat\' -Recurse
foreach ($username in $usernames)
{
ForEach-Object {Copy-Item $file.FullName ('C:\Users\Admin\Desktop\generatedbat\' + $username + $File.BaseName + ".bat")}
}
This copies everything and it kind of works but I have one problem.
Instead of having this filename: JohnR-VPNNEW_up.bat
I get this: #{Username=JohnR}-VPNNEW_up.bat
Any help? Thanks!
So you have one .bat file C:\Users\Admin\Desktop\generatedbat\VPNNEW_up.bat you want to copy to the same directory with new names taken from the usernames.csv --> Username column.
Then try
# get an array of just the UserNames column in the csv file
$usernames = (Import-Csv -Path 'C:\Users\Admin\Desktop\usernames.csv').Username
# get the file as object so you can use its properties
$originalFile = Get-Item -Path 'C:\Users\Admin\Desktop\generatedbat\VPNNEW_up.bat'
foreach ($username in $usernames) {
$targetFile = Join-Path -Path $originalFile.DirectoryName -ChildPath ('{0}-{1}' -f $username, $originalFile.Name)
$originalFile | Copy-Item -Destination $targetFile -WhatIf
}
I have added switch -WhatIf so you can first test this out. If what is displayed in the console window looks OK, then remove that -WhatIf safety switch and run the code again so the file is actually copied
I kept the code the same but instead of using a .csv file I just used a .txt file and it worked perfectly.

Powershell: Converting Headers from .msg file to .txt - Current directory doesn't pull header information, but specific directory does

So I am trying to make a script to take a batch of .msg files, pull their header information and then throw that header information into a .txt file. This is all working totally fine when I use this code:
$directory = "C:\Users\IT\Documents\msg\"
$ol = New-Object -ComObject Outlook.Application
$files = Get-ChildItem $directory -Recurse
foreach ($file in $files)
{
$msg = $ol.CreateItemFromTemplate($directory + $file)
$headers = $msg.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
$headers > ($file.name +".txt")
}
But when I change the directory to use the active directory where the PS script is being run from $directory = ".\msg\", it will make all the files into text documents but they will be completely blank with no header information. I have tried different variations of things like:
$directory = -Path ".\msg\"
$files = Get-ChildItem -Path $directory
$files = Get-ChildItem -Path ".\msg\"
If anyone could share some ideas on how I could run the script from the active directory without needing to edit the code to specify the path each location. I'm trying to set this up so it can be done by simply putting it into a folder and running it.
Thanks! Any help is very appreciated!
Note: I do have outlook installed, so its not an issue of not being able to pull the headers, as it works when specifying a directory in the code
The easiest way might actually be to do it this way
$msg = $ol.CreateItemFromTemplate($file.FullName)
So, the complete script would then look something like this
$directory = ".\msg\"
$ol = New-Object -ComObject Outlook.Application
$files = Get-ChildItem $directory
foreach ($file in $files)
{
$msg = $ol.CreateItemFromTemplate($file.FullName)
$headers = $msg.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x007D001E")
$headers > ($file.name +".txt")
}
All that said, it could be worthwhile reading up on automatic variables (Get-Help about_Automatic_Variables) - for instance the sections about $PWD, $PSScriptRoot and $PSCommandPath might be useful.
Alternative ways - even though they seem unnecessarily complicated.
$msg = $ol.CreateItemFromTemplate((Get-Item $directory).FullName + $file)
Or something like this
$msg = $ol.CreateItemFromTemplate($file.DirectoryName + "\" $file)

Download SharePoint Files-PowerShell Exception: The WriteObject and WriteError methods cannot be called from outside the overrides of the

I am receiving the following error
Get-PnPFile : The WriteObject and WriteError methods cannot be called from outside the overrides of the BeginProcessing, ProcessRecord, and EndProcessing methods, and they can only be called from within the same thread. Validate that the cmdlet makes these calls correctly, or contact Microsoft Customer Support Services.
when running this PowerShell script:
$cred = Get-Credential;
$webUrl = "https://...sharepoint.com";
$listUrl = "..";
$destination = "C:\\Folder1"
Connect-PnPOnline -Url $webUrl -Credentials $cred
$web = Get-PnPWeb
$list = Get-PNPList -Identity $listUrl
function ProcessFolder($folderUrl, $destinationFolder) {
$folder = Get-PnPFolder -RelativeUrl $folderUrl
$tempfiles = Get-PnPProperty -ClientObject $folder -Property Files
if (!(Test-Path -Path $destinationfolder)) {
$dest = New-Item $destinationfolder -Type Directory
}
$total = $folder.Files.Count
for ($i = 0; $i -lt $total; $i++) {
$file = $folder.Files[$i]
Get-PnPFile -ServerRelativeUrl $file.ServerRelativeUrl -Path
$destinationfolder -FileName $file.Name -AsFile
}
}
function ProcessSubFolders($folders, $currentPath) {
foreach ($folder in $folders) {
$tempurls = Get-PnPProperty -ClientObject $folder -Property ServerRelativeUrl
# Avoid Forms folders
if ($folder.Name -ne "Forms") {
$targetFolder = $currentPath +"\"+ $folder.Name;
ProcessFolder
$folder.ServerRelativeUrl.Substring($web.ServerRelativeUrl.Length)
$targetFolder
$tempfolders = Get-PnPProperty -ClientObject $folder -Property Folders
ProcessSubFolders $tempfolders $targetFolder
}
}
}
# Download root files
ProcessFolder $listUrl $destination + "\"
# Download files in folders
$tempfolders = Get-PnPProperty -ClientObject $list.RootFolder -Property Folders
ProcessSubFolders $tempfolders $destination + "\"
This script works as expected on a Win10 PC but not on a Win Server. Can anybody tell me what the reason could be please?
After trying this again this morning, the above script works on both a Windows 10 PC and Windows Server,
Alexandra
This may help people looking for that Exact Exception, in some cases. The Exception does not seem to explain what was actually happening so here's some background for people to see if the situation is similar to mine:
It was Generated when I was using Powershell and Get-PNPFile to get files in bulk from a Sharepoint site. After some head scratching it seemed to be related to the existence of the file already being present on the local system from a previous download. Since the script was being used to keep a local copy of certain folders on the sharepoint site I was trying to overwrite the files locally if the server files were newer. (A bit like a crude rsync).
It appears Get-PnpFile does not clobber (silently overwrite) the file if it already exists, but generates this exception. A synopsis of the code that ran without error follows:
Try {
$local_path = ($local_dir + $local_file)
#test for the local file first and rename it
if( Test-Path ( $local_path ) -PathType Leaf ){
Rename-Item $local_path ($local_path + '.old' )
}
#get the file
Get-PnpFile -ServerRelativeUrl $file.ServerRelativeUrl -Path $local_dir -FileName $local_file -AsFile
#remove the old file
if( Test-Path ( $local_path ) -PathType Leaf ){
Remove-Item ($local_path + '.old' )
}
}
Catch {
#handle error and continue
}
Essentially test for the local file first and rename it, download the file, delete the renamed local file. I wrapped it in a Try catch block so that if the download failed for this file I could continue as part of a loop and later I could search for any filenames with a .old suffix and recover the most recent local files for the failures, making note of those.
When the script was re-run the script again which would not see the remote file locally (since the names wouldn't match and fetch a fresh copy but clear the .old ( from a previous run ) file away if it succeeded in getting the file.
I didn't see the exception again.
Hope that helps somebody.
P.S. (if any of your files end in .old Strange/Unknown things WILL happen with the simple method above ....you have been warned)
Update
I'll update here whenever I see a new cause of this Exception
Update
It appears "[" or "]" square brackets in the file name may also cause this. Perhaps in the URL needs urlencoding or escaping....

PowerShell: How to copy only ".exe" file from ZIP folder to another folder

I have a "ZIP" file and when we extract this, we have 1 "EXE" file within 4-5 sub folder depth level.
I would like to grab that "EXE" file and copy into another folder. How to do it using PowerShell?
I tried below, but it will copy all the ZIP content,
$shell = New-Object -ComObject shell.application
$zip = $shell.NameSpace("Source Path")
foreach ($item in $zip.items()) {
$shell.Namespace("Destination Path").CopyHere($item)
}
Simple snippet should get your job done
#Sets the variable to the Source folder, recurse drills down to folders within
$Source = get-childitem "C:\Users" -recurse #"C:\Users" an example
#Filters by extension .exe
$List = $Source | where {$_.extension -eq ".exe"}
#Copies all the items to the specified destination
$List | Copy-Item -Destination "C:\Scripts" #"C:\Scripts" an example
The module above scans for every single .EXE files within C:\Users* and copies them to C:\Scripts
As it stands, Clint's answer did not work for me, but something based on Extract Specific Files from ZIP Archive does, with a variation to target a specifically named file. Will need a further tweak to handle multiple files sharing the same name.
Code:
# Set source zip path, target output directory and file name filter
$ZipPath = 'C:\temp\Test.zip'
$OutDir = 'C:\temp'
$Filter = 'MyExe.exe'
# Load compression methods
Add-Type -AssemblyName System.IO.Compression.FileSystem
# Open zip file for reading
$Zip = [System.IO.Compression.ZipFile]::OpenRead($Path)
# Copy selected items to the target directory
$Zip.Entries |
Where-Object { $_.FullName -eq $Filter } |
ForEach-Object {
# Extract the selected items from the zip archive
# and copy them to the out folder
$FileName = $_.Name
[System.IO.Compression.ZipFileExtensions]::ExtractToFile($_, "$OutDir\$FileName", $true)
}
# Close zip file
$Zip.Dispose()

Powershell: How to get file extensions of public folder attachments

i wonder if there is a way to get the file extension from a public folder attachment?
Backgroud:
We are using a software (AttachmentsProcessor) which extracts all attachments from the e-mails in the public folder structure and save it to the filesystem. The software puts a .lnk in the e-mail, which points to the location in the filesystem. So we can open the attachment by double-click.
Lately we moved our public folder structure from internal Exchange to Office365 / Exchange Online. During this process we tried to put all extracted attachments back into the e-mails. After we done some tests, we noticed the this didn't work for some of the e-mails. We have still the .lnk as an attachment.
So what am I looking for?
I would like to write a script in powershell which shows me a list of all e-mails and the corresponding folders (Identites), which have a .lnk file attached.
On my search I just found something that works for mailboxes but nothing for public folders.
-> Get-Mailbox | Export-Mailbox -AttachmentFilenames "*.PDF"
-> Get-Mailbox | New-MailboxExportRequest -ContentFilter {Attachment -like "*.PDF"}
Any help would be very nice. ;-)
Thanks for your attention
Peter
I can't overtly write all the code for you. But I have something that can get you started. This script will recursively iterate through your public folders and find items that have attachments on them. The last bit of code inside the loop currently saves the files to disk, but that's where you can replace that with some logic to do what you need it to do (i.e. filtering by attachment, pulling the link information, etc.).
$TargetDirectory = "C:\temp\PublicFolders"
function process-folders-recursive($folder, $path) {
if($folder.DefaultMessageClass -ne "IPM.Appointment" -and $folder.DefaultMessageClass -ne "IPM.Contact")
{
$path = (Join-Path $path $folder.name)
write-host $folder.class $path
if($folder.Items.Count -gt 0 ){
foreach($item in $folder.Items){
if($item.MessageClass -ne "IPM.Appointment")
{
#Write-Host $item.name
if($item.Attachments.Count -gt 0 ) {
if(!(Test-Path -path $path)){
New-Item -ItemType directory -Path $path
}
foreach($attch in $item.Attachments){
try
{
Write-Host $attch.FileName
$fileName = $attch.FileName
$fileNameAndPath = (Join-Path $path $fileName)
$attch.saveasfile($fileNameAndPath)
}
catch [System.Exception]
{
Write-Host $path $fileName # $_.Exception.ToString()
}
}
}
}
}
}
$folder.Folders | ForEach { process-folders-recursive $_ $path}
}
}
$objOutlook = New-Object -comobject outlook.application
$objNamespace = $objOutlook.GetNamespace(“MAPI”)
#Get Outlook folder with name Public Folders
$publicFolders = $objNamespace.Folders | Where { $_.name.StartsWith("Public Folders") } | Select -f 1
#Go into the "All Public Folders" folder
$AllPublicFolders = $publicFolders.Folders | Where { $_.name -eq "All Public Folders" } | Select -f 1
#Recurse through the Public Folders
process-folders-recursive $AllPublicFolders $TargetDirectory