Powershell Save off attachments in Subfolders in Outlook - email

A little background first.
In my Inbox I have a Subfolder called One
Inside this is an email from someone with an attachment called One.pdf
In my Inbox I have a Subfolder called Two
Inside this is an email from someone with an attachment called Two.pdf
In my Inbox I have a Subfolder called Own inside this I have a Subfolder called Three.
Inside this is an email from someone with an attachment called Three.pdf
In my Inbox I have am Email with an attachment called Four.pdf
Inbox
-----One
--------Three
-----Two
Still with me? :)
I have a requirement to do the following.
I need to parse through the inbox and find the .PDF attachment and save it to another location on a drive.
Then I need to parse through the Subfolders of the inbox. If I find a .pdf I need to do two things.
I need to check if the folders exists and if not create it.
I need to then save the .PDF in that Subfolder to the folder I just created.
Currently I can create the Subfolders.
My problem is I am unable to create the correct files in each sub folder. Infact right now am only able to create four.pdf and I can create that in every sub folder.
Currently I am working with this code.
$O=0
$Obj = New-Object -comobject outlook.application
$Name = $Obj.GetNamespace(“MAPI”)
$Mail = $Name.pickfolder()
$Path = "C:\Attached\"
$SubFolder = {
param(
$currentFolder
)
foreach ($item in $currentFolder.Folders) {
$Mail.Items | ForEach {
$O=$O+1
$_.Attachments | foreach {
$item.FolderPath
& $SubFolder $item
}
}
}
}
$Walk = & $SubFolder $Mail
ForEach ($Fo in $Walk){
$Fo.Items | ForEach {
$O=$O+1
$_.Attachments | foreach {
$Sub = $Fo
$Pos = $Sub.IndexOf("\")
$LeftPart = $Sub.Substring($Pos+28)
$FilePath = $Path + $LeftPart + "\"
If ($_.filename -like "*.PDF") {
$_
If (!(Test-Path -path $FilePath))
{
$Dest = New-Item $FilePath -type directory
}
$_.saveasfile((Join-Path $FilePath $_.filename))
}
}
}
}
This allows the selection of the Outlook folder and then does nothing. If I change $Fo.Items to $Mail.items it creates the folders and the four.pdf
I know $Fo isn't what I want it to be but am not sure what differection to take from here.
Any advice please.
Thanks

Not sure why I was voted down, be intrested to know?
Anyway. I can to this solution for my issue.
Each "section" will drill down a sub folder level, build the folder structure then save off the attahments based on type. Its by no means pretty but it does the job I need. I have posted it here for reference.
$O=0
$Outlook = New-Object -com "Outlook.Application"
$NameSpace = $Outlook.GetNamespace("MAPI")
$Mail = $NameSpace.pickfolder()
$Parent = $Mail.Folders
$Path = “C:\Attached\”
ForEach ($Folder in $Parent) {
#Parent Folder.
ForEach($SubFolder in $Folder) {
#First subfolder.
ForEach ($Item in $SubFolder.Items){
$O=$O+1
$Sub = $SubFolder.FolderPath
$LeftPart = $Sub.Substring($Pos+28)
$FilePath = $Path + $LeftPart + "\"
If (!(Test-Path -path $FilePath))
{
$Dest = New-Item $FilePath -type directory
}
foreach ($Attach in $Item.Attachments){
If ($Attach.filename -like "*.PDF") {
$Attach.saveasfile((Join-Path $FilePath $Attach.filename))
}
If ($Attach.filename -like "*.doc*") {
$Attach.saveasfile((Join-Path $FilePath $Attach.filename))
}
}
}
ForEach ($SubSubFolder in $SubFolder.Folders){
#Second Subfolder.
ForEach ($SubItem in $SubSubFolder.Items){
$1=$1+1
$Sub = $SubSubFolder.FolderPath
$LeftPart = $Sub.Substring($Pos+28)
$FilePath = $Path + $LeftPart + "\"
If (!(Test-Path -path $FilePath))
{
$Dest = New-Item $FilePath -type directory
}
foreach ($Attach in $SubItem.Attachments){
If ($Attach.filename -like "*.PDF") {
$Attach.saveasfile((Join-Path $FilePath $Attach.filename))
}
If ($Attach.filename -like "*.doc*") {
$Attach.saveasfile((Join-Path $FilePath $Attach.filename))
}
}
}
}
}
}

Related

Windows Power Shell rename files

I am sort of new to scripting and here's my task:
A folder with X files. Each file contains some Word documents, Excel sheets, etc. In these files, there is a client name and I need to assign an ID number.
This change will affect all the files in this folder that contain this client's name.
How can do this using Windows Power Shell?
$configFiles = Get-ChildItem . *.config -rec
foreach ($file in $configFiles)
{
(Get-Content $file.PSPath) |
Foreach-Object { $_ -replace " JOHN ", "123" } |
Set-Content $file.PSPath
}
Is this the right approach ?
As #lee_Daily pointed out you would need to have different code to perform a find and replace in different file types. Here is an example of how you could go about doing that:
$objWord = New-Object -comobject Word.Application
$objWord.Visible = $false
foreach ( $file in (Get-ChildItem . -r ) ) {
Switch ( $file.Extension ) {
".config" {
(Get-Content $file.FullName) |
Foreach-Object { $_ -replace " JOHN ", "123" } |
Set-Content $file.FullName
}
{('.doc') -or ('.docx')} {
### Replace in word document using $file.fullname as the target
}
{'.xlsx'} {
### Replace in spreadsheet using $file.fullname as the target
}
}
}
For the actual code to perform the find and replace, i would suggest com objects for both.
Example of word find and replace https://codereview.stackexchange.com/questions/174455/powershell-script-to-find-and-replace-in-word-document-including-header-footer
Example of excel find and replace Search & Replace in Excel without looping?
I would suggest learning the ImportExcel module too, it is a great tool which i use a lot.
For Word Document : This is what I'm using. Just can't figure out how this script could also change Header and Footer in a Word Document
$objWord = New-Object -comobject Word.Application
$objWord.Visible = $false
$list = Get-ChildItem "C:\Users\*.*" -Include *.doc*
foreach($item in $list){
$objDoc = $objWord.Documents.Open($item.FullName,$true)
$objSelection = $objWord.Selection
$wdFindContinue = 1
$FindText = " BLAH "
$MatchCase = $False
$MatchWholeWord = $true
$MatchWildcards = $False
$MatchSoundsLike = $False
$MatchAllWordForms = $False
$Forward = $True
$Wrap = $wdFindContinue
$Format = $False
$wdReplaceNone = 0
$ReplaceWith = "help "
$wdFindContinue = 1
$ReplaceAll = 2
$a = $objSelection.Find.Execute($FindText,$MatchCase,$MatchWholeWord, `
$MatchWildcards,$MatchSoundsLike,$MatchAllWordForms,$Forward,`
$Wrap,$Format,$ReplaceWith,$ReplaceAll)
$objDoc.Save()
$objDoc.Close()
}
$objWord.Quit()
What If I try to run on C# ? Is anything else missing?
}
string rootfolder = #"C:\Temp";
string[] files = Directory.GetFiles(rootfolder, "*.*",SearchOption.AllDirectories);
foreach (string file in files)
{ try
{ string contents = File.ReadAllText(file);
contents = contents.Replace(#"Text to find", #"Replacement text");
// Make files writable
File.SetAttributes(file, FileAttributes.Normal);
File.WriteAllText(file, contents);
}
catch (Exception ex)
{ Console.WriteLine(ex.Message);
}
}

Check if word file is password protected in powershell

I've written a script to recursively loop through every directory, find word files and append "CONFIDENTIAL" to the footer. This worked fine until it came across an encrypted file which caused the script to hang and when I clicked cancel on the password prompt, it caused the script to crash. I've attempted to
check if the document is encrypted before opening it but the prompt still opens which crashes the script. Is there a reliable way to check if the document is password protected that will work on .doc and .docx files? I've already tried using the code in the other thread and the first two methods don't work, the third method detects every file as encrypted because it throws an exception.
Current code:
$word = New-Object -ComObject Word.Application
$word.Visible = $false
$files = Get-ChildItem -Recurse C:\temp\FooterDocuments -include *.docx,*.doc
$restricted = "CONFIDENTIAL"
foreach ($file in $files) {
$filename = $file.FullName
Write-Host $filename
try {
$document = $word.Documents.Open($filename, $null, $null, $null, "")
if($document.ProtectionType -ne -1) {
$document.Close()
Write-Host "$filename is encrypted"
continue
}
} catch {
Write-Host "$filename is encrypted"
continue
}
foreach ($section in $document.Sections) {
$footer = $section.Footers.Item(1)
$footer.Range.Characters.Last.InsertAfter("`n" + $restricted)
}
$document.Save()
$document.Close()
}
$word.Quit()
You can just use get-content.
$filelist = dir c:\tmp\*.docx
foreach ($file in $filelist) {
[pscustomobject]#{
File = $file.FullName
HasPassword = [bool]((get-content $file.FullName) -match "http://schemas.microsoft.com/office/2006/keyEncryptor/password" )
}
}
sample output:
File HasPassword
---- -----------
C:\tmp\New Microsoft Word Document (2).docx False
C:\tmp\New Microsoft Word Document.docx True

Replace specific Text in Header, Footer and normal Text in Word file

I'm trying to write a PowerShell script which replaces one String with another string in word files. I need to update more than 500 word templates and files so I don't want to make it by hand. One problem is that i can't find the text in footer or header, because they are all individual and are tables with images. I manage to find the text in the normal "body" text but haven't replaced it by now. Here is my code for finding.
$path = "C:\Users\BambergerSv\Desktop\PS\Vorlagen"
$files = Get-Childitem $path -Include *dotm, *docx, *.dot, *.doc, *.DOT, *DOTM, *.DOCX, *.DOC -Recurse |
Where-Object { !($_.PSIsContainer) }
$application = New-Object -ComObject Word.Application
$application.Visible = $true
$findtext = "www.subdomain.domain.com"
function getStringMatch {
foreach ($file In $files) {
#Write-Host $file.FullName
$document = $application.Documents.Open($file.FullName, $false, $true)
if ($document.Content.Text -match $findtext) {
Write-Host "found text in file " $file.FullName "`n"
}
try {
$application.Documents.Close()
} catch {
continue
Write-Host $file.FullName "is a read only file" #if it is write protected because of the makros
}
}
$application.Quit()
}
getStringMatch
I've searched on internet.I found an answer to this question.
At first you need to understand VBA. Write the below macro in MS WORD and then save it.
Public Function CustomReplace(findValue As String, replaceValue As String) As String
For Each myStoryRange In ActiveDocument.StoryRanges
myStoryRange.find.Execute FindText:=findValue, Forward:=True, ReplaceWith:=replaceValue, replace:=wdReplaceAll
While myStoryRange.find.Found
myStoryRange.find.Execute FindText:=findValue, Forward:=True, ReplaceWith:=replaceValue, replace:=wdReplaceAll
Wend
While Not (myStoryRange.NextStoryRange Is Nothing)
Set myStoryRange = myStoryRange.NextStoryRange
myStoryRange.find.Execute FindText:=findValue, Forward:=True, ReplaceWith:=replaceValue, replace:=wdReplaceAll
While myStoryRange.find.Found
myStoryRange.find.Execute FindText:=findValue, Forward:=True,ReplaceWith:=replaceValue, replace:=wdReplaceAll
Wend
Wend
Next myStoryRange
CustomReplace = ActiveDocument.FullName
End Function
After the above macro added to MS WORD, go to Powershell and execute the below code.
$word = New-Object -ComObject Word.Application
$word.visible=$false
$files = Get-ChildItem "C:\Users\Ali\Desktop\Test" -Filter *.docx
$find=[ref]"Hello"
$replace=[ref]"Hi"
for ($i=0; $i -lt $files.Count; $i++) {
$filename = $files[$i].FullName
$doc = $word.Documents.Open($filename)
$word.Run("CustomReplace",$find,$replace)
$doc.Save()
$doc.close()
}
$word.quit()

How to extract metadata using a specific filename (get-childitem) rather than looping through ComObject namespace items

I have found multiple code snippets to scroll through a folder and display the metadata of each item in the folder, like this:
function funLine($strIN)
{
$strLine = "=" * $strIn.length
Write-Host -ForegroundColor Yellow "`n$strIN"
Write-Host -ForegroundColor Cyan $strLine
}
$sfolder = "S:\Temp"
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($sFolder)
foreach ($strFileName in $objFolder.items())
{funline "$($strFileName.name)"
for ($a ; $a -le 266; $a++)
{
$a
if($objFolder.getDetailsOf($strFileName, $a))
{
$hash += #{ $($objFolder.getDetailsOf($objFolder.items, $a)) = $a.tostring() + $($objFolder.getDetailsOf($strFileName, $a)) }
$hash | out-file c:\temp\output.txt -Append
$hash.clear()
}
}
$a=0
}
But in my script, I would like to loop through the folder(s) using Get-ChildItem and for selected files, I would like to use the getDetailsOf() to extract the authors of MS Office documents.
So, knowing the filename (example: $strFileName, can I skip the looping through each $strFileName in $objFolder.items() and just access the metadata details (where $a = 20) for the authors of $sFileName?
I have seen it done using "New-Object -ComObject word.application" but I believe that opens the document, so on a large file system with many files locked by users, this could be slow and painful.
Can I just jump to the index of $objFolder.items() for my selected filename?
Here, I was curious how it'd be done too so I looked it up and made a function that'll add that property to your [FileInfo] object (what's normally passed for a file by the Get-ChildItem cmdlet).
Function Get-CreatedBy{
[cmdletbinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName=$true)]
[Alias("Path")]
[string[]]$FullName
)
Begin{
$Shell = New-Object -ComObject Shell.Application
}
Process{
ForEach($FilePath in $FullName){
$NameSpace = $Shell.NameSpace((Split-Path $FilePath))
$File = $NameSpace.ParseName((Split-Path $FilePath -Leaf))
$CreatedBy = $NameSpace.GetDetailsOf($File,20)
[System.IO.FileInfo]$FilePath|Add-Member 'CreatedBy' $CreatedBy -PassThru
}
}
}
Then you can just pipe things to that, or specify a path directly like:
Get-ChildItem *.docx | Get-CreatedBy | FT Name,CreatedBy
or
Get-CreatedBy 'C:\Temp\File.docx' | Select -Expand CreatedBy
Edit: Fixed for arrays of files! Sorry about the previous error.
Thanks Matt! Although that question was different, it had the one piece I was looking for - how to reference $objFolder.items().item($_.Name)
So this makes a quick little snippet to display the Authors (or any other metadata field):
$FullName = "S:\Temp\filename.xlsx"
$Folder = Split-Path $FullName
$File = Split-Path $FullName -Leaf
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($Folder)
$Item = $objFolder.items().item($File)
$Author = $objFolder.getDetailsOf($Item, 20)
Write-Host "$FullName is owned by $Author"
Where Author is the 20th metadata item.

Powershell to get metadata of files

I am looking to get metadata of a specified file (or directory of files). I am specifically looking for "Program Description" on .WTV files.
I found code, but it doesn't list that attribute. Some of that code looks like this:
foreach($sFolder in $folder)
{
$a = 0
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($sFolder)
foreach ($strFileName in $objFolder.items())
{ FunLine( "$($strFileName.name)")
for ($a ; $a -le 266; $a++)
{
if($objFolder.getDetailsOf($strFileName, $a))
{
$hash += #{ `
$($objFolder.getDetailsOf($objFolder.items, $a)) =`
$($objFolder.getDetailsOf($strFileName, $a))
} #end hash
$hash
$hash.clear()
I can see that attribute in File explorer.
The code you had #user1921849 has almost got it, but to address the original question more clearly you should use the Windows Shell Property System named properties, listed in the Windows devloper documentation for WTV files, and used in line 4 below.
$shell = new-object -com shell.application
$folder = $shell.namespace("\\MEDIA\Recorded Tv\")
$item = $folder.Items().Item('Person of Interest_WBBMDT_2013_11_26_21_01_00.wtv')
write-output $item.ExtendedProperty('System.RecordedTV.ProgramDescription')
Updated URL to docs
General properties list https://learn.microsoft.com/en-us/windows/desktop/properties/props
WTV properties list https://learn.microsoft.com/en-us/windows/desktop/properties/recordedtv-bumper
Grab TagLib# from Nuget or various other places. Then check out this blog post showing how to use TagLib# to edit MP3 tags. Hopefully, it can retrieve the WTV tag you're looking for.
$shell = new-object -comobject shell.application
$ShFolder=$shell.namespace("\\MEDIA\Recorded Tv\")
$ShFile =$ShFolder.parsename("Person of Interest_WBBMDT_2013_11_26_21_01_00.wtv")
$count = 0
while ($count -le 294)
{
$ShRating = $ShFolder.getdetailsof($ShFile,$count)
$count
$ShRating
$count = $count+1
}
Program Description is item 272.
I have done a sample code which will check all file in a folder and export a csv file with all the metadata details. Please find the following powershell script.
Step 1. Create a file Fileproperty.ps1
Import-Module ".\Module\AddModule.psm1" -Force
$commands = {
$source = read-host "Enter folder path "
if ([string]::IsNullOrWhitespace($source)){
Write-host "Invalid file path, re-enter."
$source = $null
&$commands
}else{
$output = read-host "Enter output folder path "
if ([string]::IsNullOrWhitespace($output)){
Write-host "Invalid output path, re-enter."
$output = $null
&$commands
}else{
$outputfilename = read-host "Enter output file name "
if ([string]::IsNullOrWhitespace($outputfilename)){
Write-host "Invalid file name, re-enter."
$outputfilename = $null
&$commands
}else{
Get-FileMetaData -folder $source | Export-Csv -Path $output\$outputfilename.csv -Encoding ascii -NoTypeInformation
Write-host "Process has been done..."
}
}
}
}
&$commands
Step 2. Create a folder Module
Step 3. create another file Module/AddModule.psm1
$FunctionFiles = #(Get-ChildItem -Path $PSScriptRoot\*.ps1 -ErrorAction SilentlyContinue)
Foreach($fileItem in #($FunctionFiles))
{
Try
{
. $fileItem.fullname
}
Catch
{
Write-Error -Message "Vsts module -> Unable to import a function in file $($fileItem.fullname): $_"
}
}
Export-ModuleMember -Function $FunctionFiles.Basename
Step 4. create another file Module/Get-FileMetaData.ps1
Function Get-FileMetaData
{
Param([string[]]$folder)
$OutputList = New-Object 'System.Collections.generic.List[psobject]'
foreach($sFolder in $folder) {
$a = 0
$objShell = New-Object -ComObject Shell.Application
$objFolder = $objShell.namespace($sFolder)
foreach ($File in $objFolder.items())
{
$FileMetaData = New-Object PSOBJECT
for ($a ; $a -le 266; $a++)
{
if($objFolder.getDetailsOf($File, $a))
{
$hash += #{$($objFolder.getDetailsOf($objFolder.items, $a)) =
$($objFolder.getDetailsOf($File, $a)) }
$FileMetaData | Add-Member $hash
$hash.clear()
} #end if
} #end for
$a=0
$OutputList.Add($FileMetaData)
} #end foreach $file
} #end foreach $sfolder
return $OutputList
} #end Get-FileMetaData
Hope this will work for you.