I'm attempting a proof of concept for my department which attempts to extract attached files from .msg files located in a set of folders. I'm still struggling with getting up to speed with PowerShell, especially when using modules and rename features.
I found a module on-line that pretty well does everything I need except that I need a slightly different variant in the new attachment filename. i.e. Not sure how to modify line with code below...
$attFn = $msgFn -replace '\.msg$', " - Attachment - $($_.FileName)"
The code below extracts the attached files and renames them along the lines of...
An email file MessageFilename.msg, with an attachment AttachmentFilename.pdf extracts the attached filename to Messagefilename - Attachement - AttachmentFilename.pdf
I really need the attachment filename to be extracted into the format AttachmentFilename.pdf only. The problem I keep having is that I keep losing the path to the .msg filename so get errors when attempting the rename to a path that doesn't exist. I've tried a few options in debug mode but keep losing the path context when attempting the 'replace'.
Any help appreciated...
The borrowed code is...
##
## Source: https://chris.dziemborowicz.com/blog/2013/05/18/how-to-batch-extract-attachments-from-msg-files-using-powershell/
##
## Usage: Expand-MsgAttachment *
##
##
function Expand-MsgAttachment
{
[CmdletBinding()]
Param
(
[Parameter(ParameterSetName="Path", Position=0, Mandatory=$True)]
[String]$Path,
[Parameter(ParameterSetName="LiteralPath", Mandatory=$True)]
[String]$LiteralPath,
[Parameter(ParameterSetName="FileInfo", Mandatory=$True, ValueFromPipeline=$True)]
[System.IO.FileInfo]$Item
)
Begin
{
# Load application
Write-Verbose "Loading Microsoft Outlook..."
$outlook = New-Object -ComObject Outlook.Application
}
Process
{
switch ($PSCmdlet.ParameterSetName)
{
"Path" { $files = Get-ChildItem -Path $Path }
"LiteralPath" { $files = Get-ChildItem -LiteralPath $LiteralPath }
"FileInfo" { $files = $Item }
}
$files | % {
# Work out file names
$msgFn = $_.FullName
$msgFnbase = $_.BaseName
# Skip non-.msg files
if ($msgFn -notlike "*.msg") {
Write-Verbose "Skipping $_ (not an .msg file)..."
return
}
# Extract message body
Write-Verbose "Extracting attachments from $_..."
$msg = $outlook.CreateItemFromTemplate($msgFn)
$msg.Attachments | % {
# Work out attachment file name
$attFn = $msgFn -replace '\.msg$', " - Attachment - $($_.FileName)"
# Do not try to overwrite existing files
if (Test-Path -literalPath $attFn) {
Write-Verbose "Skipping $($_.FileName) (file already exists)..."
return
}
# Save attachment
Write-Verbose "Saving $($_.FileName)..."
$_.SaveAsFile($attFn)
# Output to pipeline
Get-ChildItem -LiteralPath $attFn
}
}
}
End
{
Write-Verbose "Done."
}
}
$msgFn = $_.FullName
says that it will be a full path in the form c:\path\to\file.msg.
So you can use:
# extract path, e.g. 'c:\path\to\'
$msgPath = Split-Path -Path $msgFn
# make new path, e.g. 'c:\path\to\attachment.pdf'
$newFn = Join-Path -Path $msgPath -ChildPath ($_.FileName)
Related
Is it possible to temporarily expand the contents of a .ZIP file (7-Zip) to a variable in memory, manipulate the contents and discard it, using PowerShell?
I'm currently expanding the archive which extracts a "log.dat" file. Then I read the contents of this log file, do the analysis and erase the "log.dat" file. But I have to do it 500,000 times which can be harmful to the drive. So right now my workaround for this was to create an R:\ RamDrive and use it like this
$zipFiles = Get-ChildItem -Filter '*.zip' -r
foreach($zip in $zipFiles) {
Expand-7Zip -ArchiveFileName $zip.FullName -TargetPath 'R:\'
Select-String -Path 'R:\log.dat' -Pattern "dataToSearchFor" | ForEach-Object {
# do analysis
}
Remove-Item 'R:\log.dat'
}
What I need is something like
$zipFiles = Get-ChildItem -Filter '*.zip' -r
foreach($zip in $zipFiles) {
$extractedFiles = Expand-7Zip -ArchiveFileName $zip.FullName
$logFile = $extractedFiles[0] # log.dat file is unique in file
Select-String $logFile -Pattern "dataToSearchFor" | ForEach-Object {
# do analysis
}
}
BTW: I have to use the 7-zip library for PowerShell because of the compression method used for the archives
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -Name 'PSGallery' -SourceLocation "https://www.powershellgallery.com/api/v2" -InstallationPolicy Trusted
Install-Module -Name 7Zip4PowerShell -Force
They say "The third time is a charm."
Well, this my 3rd attempt at solving this. Info for second attempt is still valid, but only for certain zip files, so you can find that info farther down in this answer.
First, install the latest version of 7-zip from https://www.7-zip.org/.
In my case, installed 7z2201-x64.exe.
Second, Download Nuget package for SevenZipSharp, then, using 7-Zip to open the package, navigate to sevenzipsharp.net45.1.0.19.nupkg\lib\net45\ and save SevenZipSharp.dll to same location as your PowerShell script.
Either of the following seems to work for the download:
https://www.nuget.org/api/v2/package/SevenZipSharp.Net45/1.0.19
Or
https://globalcdn.nuget.org/packages/sevenzipsharp.net45.1.0.19.nupkg
Third, take note of where 7-Zip's 7z.dll file is installed. In my case, it was C:\Program Files\7-Zip\7z.dll.
Forth, add the following lines to the top of your PowerShell script, making sure the path given to SetLibraryPath is set to that of 7-Zip's 7z.dll found in the Third step from above.
using namespace System.IO
Add-Type -Path "$PSScriptRoot\SevenZipSharp.dll"
[SevenZip.SevenZipExtractor]::SetLibraryPath('C:\Program Files\7-Zip\7z.dll')
Fifth, add the code you want to run.
This example reads all the file path names found in the archive file SevenZipTest.zip which is found in the same path as the PowerShell script:
function ReadFilenamesIn7Zip {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path
)
[SevenZip.SevenZipExtractor]$ZipArchive = [SevenZip.SevenZipExtractor]::new($Path)
foreach($ArchiveFileInfo in $ZipArchive.ArchiveFileData) {
$ArchiveFileInfo.FileName
}
$ZipArchive.Dispose()
}
ReadFilenamesIn7Zip "$PSScriptRoot\SevenZipTest.zip"
This example reads all the file lines from the first internal file named Test.TXT that is found in the archive file SevenZipTest.zip which is found in the same path as the PowerShell script:
function ReadFileIn7Zip {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $true, Position = 1)]
[string]$FileToUnzip,
[Parameter(Mandatory = $false, Position = 2)]
[int]$FileIndex = -1
)
[SevenZip.SevenZipExtractor]$ZipArchive = [SevenZip.SevenZipExtractor]::new($Path)
$ThisFileIndex = 0
foreach($ArchiveFileInfo in $ZipArchive.ArchiveFileData) {
$FileNameNoPath = Split-Path $ArchiveFileInfo.FileName -leaf
if($FileNameNoPath -eq $FileToUnzip) {
if($FileIndex -lt 0 -or $FileIndex -eq $ThisFileIndex) {
$MemoryStream = [System.IO.MemoryStream]::new()
$ZipArchive.ExtractFile($ArchiveFileInfo.Index, $MemoryStream)
[StreamReader]$ZipReader = [StreamReader]::new($MemoryStream)
$MemoryStream.Position = 0
while ($null -ne ($line = $ZipReader.ReadLine())) {
$line
}
$ZipReader.Dispose()
# $MemoryStream.Dispose() # Not needed: https://learn.microsoft.com/en-us/dotnet/api/system.io.memorystream?view=net-6.0#remarks
}
$ThisFileIndex++
}
}
$ZipArchive.Dispose()
}
ReadFileIn7Zip "$PSScriptRoot\SevenZipTest.zip" "Test.TXT" 0
The functionality of ReadFilenamesIn7Zip and ReadFileIn7Zip is essentially the same as the ReadFilenamesInZip and ReadFileInZip examples below. For example, if you look at the functionality of the ReadFileInZip function below, when calling it without the -FileIndex parameter, it will return all text from all files matching the -FileToUnzip parameter, which is also true for ReadFileIn7Zip.
NOTE: {Info from second attempt is below this point.}
Info below appears to be valid only for zip files compressed with * Deflate, BZip2, and LZMA
This example takes the zip file 01_SQLite.zip and searches for any file by the name App.config. This is strongly similar to a reading version, and PowerShell equivalent, of the link jdweng provided in the comments, but several modifications such as storing the file in a StringBuilder.
UPDATE: The code was working in VSCode, but discovered it wasn't working in PowerShell 5.1 Terminal. Both should be the same, but for some reason they are not - and VSCode is set to reload PowerShell prior to each run of a script, so there shouldn't be any assemblies pre-loaded.
SOLUTION: Thank you Santiago, Added Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem to the code.
Verified this worked by closing PowerShell terminal, re-open it, and running script:
using namespace System.IO
using namespace System.IO.Compression
using namespace System.IO.MemoryStream
using namespace System.Text
Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem
$ZipFilePath = "$PSScriptRoot\01_SQLite.zip"
[ZipArchive]$ZipArchive = [ZipFile]::Open($ZipFilePath, [ZipArchiveMode]::Read)
[StringBuilder]$SB = [StringBuilder]::new()
foreach($ZipEntry in $ZipArchive.Entries) {
if($ZipEntry.Name -eq "App.config") {
[StreamReader]$ZipReader = [StreamReader]::new($ZipEntry.Open())
while ($null -ne ($line = $ZipReader.ReadLine())) {
$null = $SB.AppendLine($line)
}
# Do something with the file stored in StringBuilder $SB
Write-Host "Found file $($ZipEntry.FullName)"
Write-Host $SB.ToString()
Write-Host
$null = $SB.Clear()
$ZipReader.Dispose()
}
}
$ZipArchive.Dispose()
More Versatile and Useful Code:
This function returns the file paths and names found in the Zip file:
using namespace System.IO
using namespace System.IO.Compression
Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem
function ReadFilenamesInZip {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path
)
[ZipArchive]$ZipArchive = [ZipFile]::Open($Path, [ZipArchiveMode]::Read)
foreach($ZipEntry in $ZipArchive.Entries) {
$ZipEntry.FullName
}
$ZipArchive.Dispose()
}
Example use, reading file pathnames from 01_SQLite.zip file:
$ZipFilePath = "$PSScriptRoot\01_SQLite.zip"
$FileNames = ReadFilenamesInZip -Path $ZipFilePath
$FileNames
Resulting in this output:
screenshot.png
sqlite_test.sln
sqlite_test/App.config
sqlite_test/App.xaml
sqlite_test/App.xaml.cs
sqlite_test/MainWindow.xaml
sqlite_test/MainWindow.xaml.cs
sqlite_test/packages.config
sqlite_test/Properties/AssemblyInfo.cs
sqlite_test/Properties/Resources.Designer.cs
sqlite_test/Properties/Resources.resx
sqlite_test/Properties/Settings.Designer.cs
sqlite_test/Properties/Settings.settings
sqlite_test/sqlite_test.csproj
Example use, reading file pathnames from a zip file I created named TestZip.zip:
$ZipFilePath = "$PSScriptRoot\TestZip.zip"
$FileNames = ReadFilenamesInZip -Path $ZipFilePath
$FileNames
Resulting in this output:
Folder1/Test.TXT
Folder2/Test.TXT
Test.TXT
This function returns the content of all files matching a certain file name:
using namespace System.IO
using namespace System.IO.Compression
Add-Type -Assembly System.IO.Compression, System.IO.Compression.FileSystem
function ReadFileInZip {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Path,
[Parameter(Mandatory = $true, Position = 1)]
[string]$FileToUnzip,
[Parameter(Mandatory = $false, Position = 2)]
[int]$FileIndex = -1
)
[ZipArchive]$ZipArchive = [ZipFile]::Open($Path, [ZipArchiveMode]::Read)
$ThisFileIndex = 0
foreach($ZipEntry in $ZipArchive.Entries) {
if($ZipEntry.Name -eq $FileToUnzip) {
if($FileIndex -lt 0 -or $FileIndex -eq $ThisFileIndex) {
[StreamReader]$ZipReader = [StreamReader]::new($ZipEntry.Open())
while ($null -ne ($line = $ZipReader.ReadLine())) {
$line
}
$ZipReader.Dispose()
}
$ThisFileIndex++
}
}
$ZipArchive.Dispose()
}
Example use of extracting from TestZip.zip the content of all internal file matching the file name Test.TXT:
$ZipFilePath = "$PSScriptRoot\TestZip.zip"
$FileLines = ReadFileInZip -Path $ZipFilePath -FileToUnzip 'Test.TXT'
if ($null -ne $FileLines) {
Write-Host 'Found File(s):'
$FileLines
} else {
Write-Host 'File NOT found.'
}
Resulting in this output:
Found File(s):
### Folder 1 Text File ###
Random info in Folder 1 text file
### Folder 2 Text File ###
Random info in Folder 2 text file
### Root Text File ###
Random info in root text file
Example reading the content of only the first file with matching name -
Take note of the added -FileIndex 0:
$ZipFilePath = "$PSScriptRoot\TestZip.zip"
$FileLines = ReadFileInZip -Path $ZipFilePath -FileToUnzip 'Test.TXT' -FileIndex 0
if ($null -ne $FileLines) {
Write-Host 'Found File(s):'
$FileLines
} else {
Write-Host 'File NOT found.'
}
Resulting in this output:
Found File(s):
### Folder 1 Text File ###
Random info in Folder 1 text file
Changing -FileIndex 0 to -FileIndex 2 gives these results:
Found File(s):
### Root Text File ###
Random info in root text file
Changing FileIndex to a value that does not point to a file inside the zip, such as -FileIndex 3, gives these results:
File NOT found.
It is a file organiser script I wrote for myself. For a specific purpose of mine. Whenever I try to run it It runs and closes off. But the move operation is not happening. The below comments may help you understand what the code is doing.Please help me on what am i doing wrong here. I am extremely new to Powershell Scripting.
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
# Global variable declarations
$global:pathsFromConfig = Get-Content -Path $PSScriptRoot"\MoverPaths.txt"
$global:categoriesFromConfig = Get-Content -Path $PSScriptRoot"\MoverCategories.txt"
$global:categryHash = #{}
# Method call to read configs, create dirs, & move files
readCreateAndMoveFiles
# Method definition
function readCreateAndMoveFiles{
# Reads categories config.txt and splits them line by line
# Adds each line as a key value pair to a hashtable
foreach($category in $categoriesFromConfig)
{
$temp = $category -split ":"
$categryHash.add($temp[0].trim().toString(),($temp[1]).trim().toString())
}
# For each category in the hash table, calls create directory method, and then moves the files based on current category
foreach($hashItem in $categryHash.GetEnumerator()){
# Creates a directory with the Hash Key
Foreach($pathToMonitor in $pathsFromConfig){
$categoryFullPath = $pathToMonitor+$hashItem.Name
createDirectory($categoryFullPath)
# Moves files into that directory
Set-Location -Path $pathToMonitor
$extentions = $hashItem.Value
Get-Item $extentions | Move-Item -Destination $categoryFullPath
$categoryFullPath = ""
}
}
}
# Method Definition
function createDirectory ($categoryName)
{
if(Test-Path -Path $categoryName)
{
# Directory already Exists!
}
else
{
# Creates Directory
md $categoryName
}
}
The config files are hereby:
MoverCategories.txt
Images:*.jpg,*.jpeg,*.png,*.tiff,*.raw,*.heic,*.gif,*.svg,*.eps,*.ico
Documents:*.txt,*.pdf,*.doc,*.docx,*.xls,*.xlsx,*.ppt,*.pptx,*.html,*.xls,*.csv,*.rtx
MoverPaths.txt
D:\Downloads\
Found a way to do this. Thanks for all of your input. Now the script moves files. Instead of sending all extentions in a single shot, i made it into an array and sent it one by one. Now it works fine. If you guys could help me reduce the time of execution that would be great.But the code works now I am happy.
foreach($hashItem in $categryHash.GetEnumerator()){
# Creates a directory with the Hash Key
Foreach($pathToMonitor in $pathsFromConfig){
$categoryFullPath = $pathToMonitor+$hashItem.Name
createDirectory($categoryFullPath)
# Moves files into that directory
[String[]]$extentions = #()
$extentions = $hashItem.Value -split ','
foreach($string in $extentions)
{
Get-Item $pathToMonitor\* -Include $string | Move-Item -Destination $categoryFullPath
}
}
}
Try this
#specify path(s)
$path = "$env:USERPROFILE\Downloads"
## this is make an array of the extensions in the foloder
$extensions = Get-ChildItem -Path $path | Select-Object -Unique -Property #{label = 'ext'
expression = { $_.Extension.substring(1) }
}
## this function will
function New-FoldersByName {
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Data to process')]
$InputObject
)
process {
Set-Location $path
if (!(Test-Path -PathType Container $InputObject )) {
New-Item -ItemType directory -Name $InputObject.ext -WhatIf
Write-Host -Message "A folder named $($InputObject.ext) does not exist. Creating..."
}
else {
Write-Host -Message "A folder named $($InputObject.ext) already exists. Skipping..."
}
}
}
##this is a reuseable function to moves items in a folder into a subfolder named after the files extension
## if extension is .exe the file with be moved to ./EXE/filename.exe
function Move-ItemsByName {
param
(
[Parameter(Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'Data to process')]
$InputObject
)
process {
Set-Location -Path $path
Move-Item -Path ('*.{0}' -f $InputObject.ext) -Destination ('{0}' -f $InputObject.ext) -WhatIf
}
}
$extensions | New-FoldersByName
$extensions | Move-ItemsByName
I am trying to use .NET classes instead of native compress-archive to zip multiple directories (each containing sub-directories and files), as compress-archive is giving me occasional OutOfMemory Exception.
Some articles tell me .NET classes, makes for a more optimal approach.
My tools directory $toolsDir = 'C:\Users\Public\LocalTools' has more than one directory that need to be zipped (please note everything is a directory, not file) - whichever directory matches the regex pattern as in the code.
Below is my code:
$cmpname = $env:computername
$now = $(Get-Date -Format yyyyMMddmmhhss)
$pattern = '^(19|[2-9][0-9])\d{2}\-(0?[1-9]|1[012])\-(0[1-9]|[12]\d|3[01])T((?:[01]\d|2[0-3])\;[0-5]\d\;[0-5]\d)\.(\d{3}Z)\-' + [ regex ]::Escape($cmpname)
$toolsDir = 'C:\Users\Public\LocalTools'
$destPathZip = "C:\Users\Public\ToolsOutput.zip"
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$CompressionLevel = [ System.IO.Compression.CompressionLevel ]::Optimal
$IncludeBaseDirectory = $false
$stream = New-Object System.IO.FileStream($destPathZip , [ System.IO.FileMode ]::OpenOrCreate)
$zip = New-Object System.IO.Compression.ZipArchive($stream , 'update')
$res = Get-ChildItem "${toolsDir}" | Where-Object {$_ .Name -match "${pattern}"}
if ($res -ne $null) {
foreach ($dir in $res) {
$source = "${toolsDir}\${dir}"
[ System.IO.Compression.ZipFileExtensions ]::CreateEntryFromFile($destPathZip , $source , (Split-Path $source -Leaf), $CompressionLevel)
}
}
else {
Write-Host "Nothing to Archive!"
}
Above code gives me this error:
When I researched about [ System.IO.Compression.ZipFileExtensions ]::CreateEntryFromFile , it is used to add files to a zip file already created. Is this the reason I am getting the error that I get ?
I also tried [ System.IO.Compression.ZipFile ]::CreateFromDirectory($source , $destPathZip , $CompressionLevel, $IncludeBaseDirectory) instead of [ System.IO.Compression.ZipFileExtensions ]::CreateEntryFromFile($destPathZip , $source , (Split-Path $source -Leaf), $CompressionLevel)
That gives me "The file 'C:\Users\Public\ToolsOutput.zip' already exists error.
How to change the code, in order to add multiple directories in the zip file.
There are 3 problems with your code currently:
First argument passed to CreateEntryFromFile() must be a ZipArchive object in which to add the new entry - in your case you'll want to pass the $zip which you've already created for this purpose.
CreateEntryFromFile only creates 1 entry for 1 file per call - to recreate a whole directory substructure you need to calculate the correct entry path for each file, eg. subdirectory/subsubdirectory/file.exe
You need to properly dispose of both the ZipArchive and the underlying file stream instances in order for the data to be persisted on disk. For this, you'll need a try/finally statement.
Additionally, there's no need to create the file if there are no files to archive :)
$cmpname = $env:computername
$pattern = '^(19|[2-9][0-9])\d{2}\-(0?[1-9]|1[012])\-(0[1-9]|[12]\d|3[01])T((?:[01]\d|2[0-3])\;[0-5]\d\;[0-5]\d)\.(\d{3}Z)\-' + [regex]::Escape($cmpname)
$toolsDir = 'C:\Users\Public\LocalTools'
$destPathZip = "C:\Users\Public\ToolsOutput.zip"
Add-Type -AssemblyName System.IO.Compression
Add-Type -AssemblyName System.IO.Compression.FileSystem
$CompressionLevel = [System.IO.Compression.CompressionLevel]::Optimal
$res = Get-ChildItem -LiteralPath $toolsDir | Where-Object { $_.Name -match $pattern }
if ($res) {
try {
# Create file + zip archive instances
$stream = New-Object System.IO.FileStream($destPathZip, [System.IO.FileMode]::OpenOrCreate)
$zip = New-Object System.IO.Compression.ZipArchive($stream, [System.IO.Compression.ZipArchiveMode]::Update)
# Discover all files to archive
foreach ($file in $res |Get-ChildItem -File -Recurse) {
$source = $dir.FullName
# calculate correct relative path to the archive entry
$relativeFilePath = [System.IO.Path]::GetRelativePath($toolsDir, $source)
$entryName = $relativeFilePath.Replace('\', '/')
# Make sure the first argument to CreateEntryFromFile is the ZipArchive object
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zip, $source, $entryName, $CompressionLevel)
}
}
finally {
# Clean up in reverse order
$zip, $stream | Where-Object { $_ -is [System.IDisposable] } | ForEach-Object Dispose
}
}
else {
Write-Host "Nothing to Archive!"
}
Calling Dispose() on $zip will cause it to flush any un-written modifications to the underlying file stream and free any additional file handles it might have acquired, whereas calling Dispose() on the underlying file stream flushes those changes to the disk and closes the file handle.
PowerShell novice here again with my proof of concept.
The code below successfully extracts attached files from .msg files located in folders and leaves the extracted filename without changing it. What I'm now looking for now is to extract part of the parent folder name, with standard format of...
nnnn+string (e.g. "8322 MyStudy") i.e. 4 digits followed by a space then string.
...to rename the extracted filename from...
ExtractedFilename.pdf to "0nnnn - ExtractedFilename.pdf". e.g. "08322 - ExtractedFilename.pdf"
My main problem is how to extract the numeric part of the parent folder name (from where my module will be run). I'm hoping that my poor PS formatting skills will allow me to do the rest.
Once again, any help appreciated.
##
## Source: https://chris.dziemborowicz.com/blog/2013/05/18/how-to-batch-extract-attachments-from-msg-files-using-powershell/
##
## Usage: Expand-MsgAttachment *
##
##
function Expand-MsgAttachment
{
[CmdletBinding()]
Param
(
[Parameter(ParameterSetName="Path", Position=0, Mandatory=$True)]
[String]$Path,
[Parameter(ParameterSetName="LiteralPath", Mandatory=$True)]
[String]$LiteralPath,
[Parameter(ParameterSetName="FileInfo", Mandatory=$True, ValueFromPipeline=$True)]
[System.IO.FileInfo]$Item
)
Begin
{
# Load application
Write-Verbose "Loading Microsoft Outlook..."
$outlook = New-Object -ComObject Outlook.Application
}
Process
{
switch ($PSCmdlet.ParameterSetName)
{
"Path" { $files = Get-ChildItem -Path $Path }
"LiteralPath" { $files = Get-ChildItem -LiteralPath $LiteralPath }
"FileInfo" { $files = $Item }
}
$files | % {
# Work out file names
$msgFn = $_.FullName
# extract path, e.g. 'c:\path\to\'
$msgPath = Split-Path -Path $msgFn
# Skip non-.msg files
if ($msgFn -notlike "*.msg") {
Write-Verbose "Skipping $_ (not an .msg file)..."
return
}
# Extract message body
Write-Verbose "Extracting attachments from $_..."
$msg = $outlook.CreateItemFromTemplate($msgFn)
$msg.Attachments | % {
# Work out attachment file name
#$attFn = $msgFn -replace '\.msg$', " - Attachment - $($_.FileName)"
$attFn = Join-Path -Path $msgPath -ChildPath ($_.FileName)
# Do not try to overwrite existing files
if (Test-Path -literalPath $attFn) {
Write-Verbose "Skipping $($_.FileName) (file already exists)..."
return
}
# Save attachment
Write-Verbose "Saving $($_.FileName)..."
$_.SaveAsFile($attFn)
# Output to pipeline
Get-ChildItem -LiteralPath $attFn
}
}
}
# This function to rename expanded attachment file to study renaming standards
Function RenameExpandedAttachments {
}
End
{
Write-Verbose "Done."
}
}
The currently running script is :
$script:MyInvocation.MyCommand.Path
Use Split-Path to get only the Path,
Split-Path $script:MyInvocation.MyCommand.Path
to get only the last element use again Split-Path with the -Leaf parameter
Split-Path -Leaf (Split-Path $script:MyInvocation.MyCommand.Path)
To extract leading numbers use a Regular Expression with a (capture group).
'^(\d+) (.*)$'
And wrap all this in an if:
If ((Split-Path -Leaf (Split-Path $script:MyInvocation.MyCommand.Path)) -match '^(\d+) (.*)$'){
$NewName = "{0:00000} - {1}" -f $Matches[1],$ExtractedFileName
} else {
"No numbers found in this path"
}
We have a csv file with approximately 8,000 SharePoint document file URLs - the files in question they refer to have to be downloaded to a file share location, then deleted from the SharePoint. The files are not located in the same sites, but across several hundred in a server farm. We are looking to remove only the specified files - NOT the entire library.
We have the following script to effect the download, which creates the folder structure so that the downloaded files are separated.
param (
[Parameter(Mandatory=$True)]
[string]$base = "C:\Export\",
[Parameter(Mandatory=$True)]
[string]$csvFile = "c:\export.csv"
)
write-host "Commencing Download"
$date = Get-Date
add-content C:\Export\Log.txt "Commencing Download at $date":
$webclient = New-Object System.Net.WebClient
$webclient.UseDefaultCredentials = $true
$files = (import-csv $csvFile | Where-Object {$_.Name -ne ""})
$line=1
Foreach ($file in $files) {
$line = $line + 1
if (($file.SpURL -ne "") -and ($file.path -ne "")) {
$lastBackslash = $file.SpURL.LastIndexOf("/")
if ($lastBackslash -ne -1) {
$fileName = $file.SpURL.substring(1 + $lastBackslash)
$filePath = $base + $file.path.replace("/", "\")
New-Item -ItemType Directory -Force -Path $filePath.substring(0, $filePath.length - 1)
$webclient.DownloadFile($file.SpURL, $filePath + $fileName)
$url=$file.SpURL
add-content C:\Export\Log.txt "INFO: Processing line $line in $csvFile, writing $url to $filePath$fileName"
} else {
$host.ui.WriteErrorLine("Exception: URL has no backslash on $line for filename $csvFile")
}
} else {
$host.ui.WriteErrorLine("Exception: URL or Path is empty on line $line for filename $csvFile")
}
}
write-Host "Download Complete"
Is there a way we could get the versions for each file?
I have been looking for a means to carry out the deletion, using the same csv file as reference - all of the code I have seen refers to deleting entire libraries, which is not desired.
I am very new to PowerShell and am getting lost. Can anyone shed some light?
Many thanks.
This looks like it might be useful. It's a different approach and would need to be modified to pull in the file list from your CSV but it looks like it generally accomplishes what you are looking to do.
https://sharepoint.stackexchange.com/questions/6511/download-and-delete-documents-using-powershell