If you were wondering what I occasionally thought about over the holiday break, while I was scrubbing my pool tiles—ugh, it was AWS and the Get-
and Write-
S3Object cmdlets. Why wasn’t I able to get them to do what I wanted!? Let me first explain my problem, and then my solution.
When you create a folder (and yes, I realize it’s not really a folder), in the AWS Management Console, you do so by clicking the “+ Create folder” button. This guy.
Simple stuff. In my case, you choose the AES-256 encryption setting and then give the folder a name. This folder, because I created it in this manner, is returned by the Get-S3Object
cmdlet. So, let’s say we walked through this manual procedure and created a folder called S3ConsoleFolder. Here’s what Get-S3Object
would return, providing the S3 bucket, we’re calling tst-bucket, had been empty from the start.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
Because it could prove helpful, let’s say we also manually created a nested folder inside of our S3ConsoleFolder called S3CFA, as in the A folder of the S3ConsoleFolder. Consider it done. Here’s the new results of the Get-S3Object
command. As you’ll see now, we return both the top-level folder, and the newly created, nested folder.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
Now, let’s get one step closer to using Write-S3Object
. Its purpose, according to its maker, Amazon, is that it “uploads one or more files from the local file system to an S3 bucket.” That, it does. Below I’ve indicated the top-level folder, the nested folders, and the files that we’ll upload. The complete path to this folder is C:\Users\tommymaynard\Desktop\TestFolder
.
TestFolder
|__ FolderA
| |__ TestFile2.txt
| |__ TestFile3.txt
|__ FolderB
| |__ TestFile4.txt
| |__ TestFile5.txt
| |__ TestFile6.txt
| |__ FolderC
| |__ TestFile7.txt
|__ TestFile1.txt
The below Write-S3Object
command’s purpose is to get everything in the above file structure uploaded to AWS S3. Additionally, it creates the folders we need: TestFolder
, FolderA
, FolderB
, and FolderC
. I’m using a parameter hash table below to decrease the length of my command, so it’s easier to read. It’s in no way a requirement.
$Params = @{
BucketName = 'tst-bucket'
Folder = 'C:\Users\tommymaynard\Desktop\TestFolder'
KeyPrefix = (Split-Path -Path 'C:\Users\tommymaynard\Desktop\TestFolder' -Leaf).TrimEnd('\')
Recurse = $true
Region = 'us-gov-west-1'
ServerSideEncryption = 'AES256'
}
With the parameter hash table created, we’ll splat it on the Write-S3Object
cmdlet. When completed, we’ll run the Get version of the cmdlet again, to see what was done.
Write-S3Object @Params
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
TestFolder/FolderA/TestFile2.txt
TestFolder/FolderA/TestFile3.txt
TestFolder/FolderB/FolderC/TestFile7.txt
TestFolder/FolderB/TestFile4.txt
TestFolder/FolderB/TestFile5.txt
TestFolder/FolderB/TestFile6.txt
TestFolder/TestFile1.tst
Now, look at the above results and tell me what’s wrong. I’ll wait…
… Can you see it? What don’t we have included in those results, that we did when we created our folders in the AWS Management Console?
Maybe I was asking for too much, but I expected to have my folders returned on their own lines just like we do for S3ConsoleFolder/
and S3ConsoleFolder/S3CFA/
. Remember, I’m lying down on my pool deck, scrubbing the pool tiles (it’s Winter, yes, but I live in southern Arizona), and I cannot for the life of me wrap my head around why I’m not seeing those folders on. their. own. lines. I expected to see these lines within my results:
TestFolder/
TestFolder/FolderA/
TestFolder/FolderB/
TestFolder/FolderB/FolderC/
Remember, I can create folders in the AWS Management Console and it works perfectly, but not with Write-S3Object
. Well, not with Write-S3Object
the way I was using it. I finally had an idea worth trying: I needed to use Write-S3Object
to create the folders first, and then upload the files into the folders. It’s obnoxious. While that required more calls to Write-S3Object
, I was okay with it, if it could get me the results I wanted. And ultimately, get my users the results I wanted them to have.
So let’s dump my TestFolder from S3 and start over. We’re here again.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
We’ll start by creating our parameter hash table. After that, we’ll begin to use the $Param variable and its properties (its keys) to supply parameter values to parameter names. I don’t actually splat the entire hash table in this code section. Although the next three code sections go together, I’ve broken them up, so I can better explain them. This first section creates a $Path
variable, an aforementioned parameter hash table (partially based on the $Path
variable), and an If
statement. The If
statement works this way: If my S3 bucket doesn’t already include a folder called TestFolder/
, then create it.
$Path = 'C:\Users\tommymaynard\Desktop\TestFolder'
$Params = @{
BucketName = 'tst-bucket'
Folder = (Split-Path -Path $Path -Leaf).TrimEnd('\')
KeyPrefix = (Split-Path -Path $Path -Leaf).TrimEnd('\')
Recurse = $true
Region = 'us-gov-west-1'
ServerSideEncryption = 'AES256'
}
# Create top-level folder (if necessary).
If ((Get-S3Object -BucketName $Params.BucketName -Region $Params.Region).Key -notcontains "$($Params.Folder)/") {
Write-S3Object -BucketName $Params.BucketName -Region $Params.Region -Key "$($Params.Folder)/" -Content $Params.Folder -ServerSideEncryption AES256
}
Now, Get-S3Object
returns my newly created, top-level folder. It was working so far.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
TestFolder/
This second section gets all of the directory’s names from my path. If there are duplicates, and there were, they’re removed by Select-Object
‘s Unique parameter. Once I know these, I can start creating my nested folders after cleaning them up a little: splitting the path, replacing backslashes with forward slashes, and removing any forward slashes from the beginning of the path. With each of those, we’ll make sure the cleaned-up path doesn’t include two forward slashes (this would indicate it’s the top-level folder again [as TestFolder//
]), and that it doesn’t already exist.
# Create nested level folder(s) (if necessary).
$NestedPaths = (Get-ChildItem -Path $Path -Recurse).DirectoryName | Select-Object -Unique
Foreach ($NestedPath in $NestedPaths) {
$CleanNestedPath = "$(($NestedPath -split "$(Split-Path -Path $Path -Leaf)")[-1].Replace('\','/').TrimStart('/'))"
If (("$($Params.Folder)/$CleanNestedPath/" -notmatch '//') -and ((Get-S3Object -BucketName $Params.BucketName -Region $Params.Region).Key -notcontains "$($Params.Folder)/$CleanNestedPath/")) {
Write-S3Object -BucketName $Params.BucketName -Region $Params.Region -Key "$($Params.Folder)/$CleanNestedPath/" -Content $CleanNestedPath -ServerSideEncryption AES256
}
}
And, now Get-S3Object
returns my nested folders, too. Still working.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
TestFolder/
TestFolder/FolderA/
TestFolder/FolderB/
TestFolder/FolderB/FolderC/
This last section only serves to upload the files from the EC2 instance to the S3 bucket and into the folders we’ve created. Like the other code did in the last two sections, this doesn’t check that files already exist. It’ll happily write, right over them without warning. I didn’t need this protection, so I didn’t include it.
Write-S3Object -BucketName $Params.BucketName -Region $Params.Region -Folder $Path -KeyPrefix "$($Params.Folder)/" -ServerSideEncryption AES256 -Recurse
Now that my folders are created and the files are uploaded, I get the results I expect. I can see all the folders on their own lines, as well as that of the files.
(Get-S3Object -BucketName 'tst-bucket' -Region 'us-gov-west-1').Key
S3ConsoleFolder/
S3ConsoleFolder/S3CFA/
TestFolder/
TestFolder/FolderA/
TestFolder/FolderA/TestFile2.txt
TestFolder/FolderA/TestFile3.txt
TestFolder/FolderB/
TestFolder/FolderB/FolderC/
TestFolder/FolderB/FolderC/TestFile7.txt
TestFolder/FolderB/TestFile4.txt
TestFolder/FolderB/TestFile5.txt
TestFolder/FolderB/TestFile6.txt
TestFolder/TestFile1.txt
I do hope I didn’t overlook an easier way to do this, but as history has proved, it’s quite possible. Here’s to hoping this can help someone else. I felt pretty lost and confused until I figured it out. It seems to me that AWS needs to iron this one out for us. No matter how it’s used, Write-S3Object
should create “folders” in such a way that they are consistently returned by Get-S3Object
. That’s whether they’re created before files are uploaded (my fix), or simply created as files are uploaded.
And, cue the person to tell me the easier way.