Support asked for the logs. You zipped the folder, attached it, and the reply says most of it is not there. The archive really is incomplete, and Compress-Archive built it without an error, a warning or anything in the output to read.
The cause sits in one parameter. Compress-Archive -Path accepts wildcards, and its reference page documents three different results for the same folder depending on how you spell it. Two of the three are useful. The third drops every subfolder, and, measured below, one more file than the page mentions.
What follows builds a small folder, compresses it three ways, and reads each archive back without extracting it, so the difference is something you can see rather than something you have to take on trust. It also covers the thing -Update does not do, and where Expand-Archive puts the files at the far end.
Applies to: PowerShell 7 on Windows 10, Windows 11 and Windows Server. Measured on 7.4.6.
What you are going to build
One folder of your own, C:\bat\ziplab, holding a folder called Report with five files in two folders. From that you build three zip files, and you save one script, Show-ArchiveEntries.ps1, which prints what is inside an archive without extracting it.
Nothing outside C:\bat\ziplab is touched, and the last section deletes the whole folder in one command. That is the only script you are asked to keep.
Quick answer
If you only need the right spelling, it is the first of these two. Run them after the next section has created the folder. They write their own archive names, so they do not collide with the three measured further down, and neither line prints anything.
# The folder itself becomes the top level inside the zip, so whoever opens it
# gets one folder rather than five loose files in their downloads.
Compress-Archive -Path .\Report -DestinationPath .\Report.zip
# The folder's contents go in at the top level instead. Subfolders are kept.
Compress-Archive -Path .\Report\* -DestinationPath .\Report-contents.zip
.\Report\*.* because it looks like "everything in there". It is the narrowest of the three, it leaves out every subfolder, and it reports nothing while it does so.
Before you start
Three things, one command each.
- PowerShell 7. Confirm the version with
$PSVersionTable.PSVersion. Everything on this page was measured on 7.4.6, and the upgrade guide covers getting there. - A folder of your own to work in, so that nothing here goes near anything you care about. Every path after this one is relative to it.
- Permission to run the one
.ps1this page saves. The reference forSet-ExecutionPolicydescribes theProcessscope like this: "The Process scope only affects the current PowerShell session."
# The three Path rules below are documented the same way for Windows PowerShell
# 5.1, so the spelling matters there too.
$PSVersionTable.PSVersion
# A folder of your own. Everything after this line uses relative paths.
New-Item -ItemType Directory -Path 'C:\bat\ziplab' -Force | Out-Null
Set-Location 'C:\bat\ziplab'
# Lets the saved script run, in this shell only.
Set-ExecutionPolicy -Scope Process -ExecutionPolicy RemoteSigned -Force
The first line prints your version. The other three print nothing.
Build the folder to compress
Five files in two folders is enough to tell the three spellings apart: three files at the top level of Report, and a Logs subfolder with two more. One of the five, CHANGELOG, has no extension, which turns out to matter.
# Five files in two folders. CHANGELOG has no extension on purpose.
New-Item -ItemType Directory -Path '.\Report\Logs' -Force | Out-Null
'service started' | Set-Content -LiteralPath '.\Report\Logs\service.log'
'service restarted' | Set-Content -LiteralPath '.\Report\Logs\service.1.log'
'ticket 4821' | Set-Content -LiteralPath '.\Report\summary.txt'
'see summary' | Set-Content -LiteralPath '.\Report\notes.txt'
'2026-10-07 rebuilt' | Set-Content -LiteralPath '.\Report\CHANGELOG'
Get-ChildItem -Path '.\Report' -Name | Sort-Object
' in Logs:'
Get-ChildItem -Path '.\Report\Logs' -Name | Sort-Object
What to look for: four names at the top level of Report, one of which is the Logs folder, then the two files inside it.
CHANGELOG
Logs
notes.txt
summary.txt
in Logs:
service.1.log
service.log
The script that reads an archive
Save this as Show-ArchiveEntries.ps1 in C:\bat\ziplab and run it with .\Show-ArchiveEntries.ps1 -ArchivePath .\root.zip. The parameter is mandatory because there is no sensible default for it. The script opens the archive read-only and extracts nothing, so it is safe to point at a zip you are about to send.
# Show-ArchiveEntries.ps1
# Prints what is inside a .zip file without extracting it.
# Separators are printed as a forward slash, so one archive always reads
# the same way here no matter which tool wrote it.
param(
[Parameter(Mandatory = $true)]
[string]$ArchivePath
)
$full = (Resolve-Path -LiteralPath $ArchivePath).Path
$zip = [System.IO.Compression.ZipFile]::OpenRead($full)
try {
$names = @($zip.Entries | ForEach-Object { $_.FullName -replace '\\', '/' } | Sort-Object)
'{0,-10}{1}' -f 'archive', (Split-Path -Leaf $full)
'{0,-10}{1}' -f 'entries', $names.Count
foreach ($name in $names) { ' ' + $name }
}
finally {
$zip.Dispose()
}
The one line worth explaining replaces a backslash with a forward slash in each printed name. That keeps every listing further down the page comparable with every other one.
Three spellings, three archives
Same folder, three spellings, one Compress-Archive call each, then the script reads all three back. The whole point of this block is the entries line in each listing.
# Same folder, three spellings, three archives.
Compress-Archive -Path .\Report -DestinationPath .\root.zip
Compress-Archive -Path .\Report\* -DestinationPath .\star.zip
Compress-Archive -Path .\Report\*.* -DestinationPath .\stardotstar.zip
# Read each one back without extracting it.
.\Show-ArchiveEntries.ps1 -ArchivePath .\root.zip
''
.\Show-ArchiveEntries.ps1 -ArchivePath .\star.zip
''
.\Show-ArchiveEntries.ps1 -ArchivePath .\stardotstar.zip
What to look for: two of the archives hold five entries and one holds two.
archive root.zip
entries 5
Report/CHANGELOG
Report/Logs/service.1.log
Report/Logs/service.log
Report/notes.txt
Report/summary.txt
archive star.zip
entries 5
CHANGELOG
Logs/service.1.log
Logs/service.log
notes.txt
summary.txt
archive stardotstar.zip
entries 2
notes.txt
summary.txt
root.zip carries all five files with Report/ in front of each name. star.zip carries the same five files with nothing in front. Those are the two spellings that keep your data.
The difference between those two is only where the files sit once the archive is opened, and the reference page says so directly. Its first bullet reads: "To create an archive that includes the root directory, and all its files and subdirectories, specify the root directory in the Path without wildcards."
What star-dot-star actually matches
The third bullet on the same page reads: "To create an archive that only zips the files in the root directory, use the star-dot-star (*.*) wildcard. Subdirectories of the root aren't included in the archive." The part about subdirectories is exactly what the measurement shows. The part before it is where the trouble is.
* matches zero or more characters, which about_Wildcards states as the first rule in its list. So *.* matches a name that contains a dot. CHANGELOG is a file in the root directory, it contains no dot, and it is not in stardotstar.zip. The bullet does not mention that.
| Spelling of the same folder | Entries in the archive | Top level inside the zip | The Logs subfolder | The file with no extension |
|---|---|---|---|---|
-Path .\Report | 5 | Report/ | kept | kept |
-Path .\Report\* | 5 | the files themselves | kept | kept |
-Path .\Report\*.* | 2 | the files themselves | gone | gone |
*.* the way cmd.exe reads it, as "every file". In PowerShell it is a pattern that needs a literal dot in the name. Three of the five files in this lab are missing from that archive: two because they live in a subfolder, and one because nobody ever gave it an extension.
Hidden files and folders never make it in
This one is not a wildcard question and it catches people who got the spelling right. The reference page carries it as a note: "The Compress-Archive cmdlet ignores hidden files and folders when creating or updating the archive file."
So a folder somebody marked hidden years ago, or a hidden file that an application wrote next to its logs, is simply absent from the archive. The reference states it plainly, which is why there is no measurement to add here, and it offers one way out: "To ensure hidden files and folders are compressed into the archive, use the .NET API instead."
Update adds and replaces, it never removes
A week later the ticket moves on. One file should no longer be in the bundle, one new file should, and rebuilding the archive with -Update looks like the obvious move.
# The file the far end should no longer receive.
Remove-Item -LiteralPath .\Report\notes.txt
# And one that did not exist when star.zip was built.
'closed 2026-10-07' | Set-Content -LiteralPath '.\Report\resolution.txt'
Compress-Archive -Path .\Report\* -DestinationPath .\star.zip -Update
.\Show-ArchiveEntries.ps1 -ArchivePath .\star.zip
What to look for: the entries line goes from five to six, and notes.txt is still listed although it no longer exists on disk.
archive star.zip
entries 6
CHANGELOG
Logs/service.1.log
Logs/service.log
notes.txt
resolution.txt
summary.txt
-Update as "make the archive match the folder". Its description covers replacing files that have the same name and adding files that are not there yet. Removal is not in it, and the measurement above is what that absence looks like.
When you want the archive to match the folder exactly, build it again with -Force instead, which the reference describes as the parameter that overwrites an existing archive file.
Expand-Archive and where the files land
The other half of the decision belongs to whoever opens the thing. Expand-Archive documents its default as a folder named after the zip: "By default, Expand-Archive creates a folder in the current location that's the same name as the ZIP file." Give it -DestinationPath and it uses that one instead. The same entry adds: "The target folder is created if it doesn't exist."
Expand-Archive -Path .\star.zip -DestinationPath .\OutStar
Expand-Archive -Path .\root.zip -DestinationPath .\OutRoot
'{0,-12}{1}' -f 'OutStar', ((Get-ChildItem -Path .\OutStar -Name | Sort-Object) -join ' ')
'{0,-12}{1}' -f 'OutRoot', ((Get-ChildItem -Path .\OutRoot -Name | Sort-Object) -join ' ')
What to look for: one destination holds five names, the other holds exactly one.
OutStar CHANGELOG Logs notes.txt resolution.txt summary.txt
OutRoot Report
OutStar holds the files themselves, including the notes.txt that the update never removed. OutRoot holds one entry, Report, with everything inside it. That single difference is the whole practical argument for zipping the folder rather than its contents: the person at the far end can extract it anywhere without scattering five files into whatever folder they were sitting in.
Expand-Archive does not overwrite files that are already there. The reference gives -Force for that, described as the parameter to "overwrite existing files".
Clean up
The lab folder holds seven things from the sections above: the Report tree, the two folders the archives were expanded into, three zip files, and the script you saved. Two more if you ran the quick answer. One command removes all of it.
# Step out of the folder first.
Set-Location 'C:\'
Remove-Item -LiteralPath 'C:\bat\ziplab' -Recurse -Force
One thing is deliberately left behind: if C:\bat did not exist before you started, the setup command created it and this one does not remove it. The execution policy change needs no undoing, because it was scoped to the shell.
Tips and limitations
- The three
-Pathrules are documented identically for Windows PowerShell 5.1, PowerShell 7.4 and PowerShell 7.5. This is not behaviour an upgrade changes. -Pathaccepts wildcards.-LiteralPathtakes the name exactly as typed, and it is the one to use when the path arrives in a variable.- Hidden files and folders are left out of the archive. The reference states that plainly and mentions no warning for it.
-Updateadds and replaces. It does not remove.-Forcerebuilds the archive instead.Expand-Archivecreates the destination folder if it is missing, and refuses to overwrite existing files unless you pass-Force.- Both reference pages state that the API behind these cmdlets limits the maximum file size to 2 GB. Neither says whether that means the archive or one file inside it, so treat 2 GB as the ceiling for both and reach for a different tool above it.
- Read the archive before you send it. Hashing it at both ends tells you the transfer was clean, and it tells you nothing at all about whether the right files went in.
Official documentation
- Compress-Archive, PowerShell 7.4 | Microsoft Learn
- Expand-Archive, PowerShell 7.4 | Microsoft Learn
- about_Wildcards, PowerShell 7.4 | Microsoft Learn
- Compress-Archive, Windows PowerShell 5.1 | Microsoft Learn
- ZipArchive Class, .NET | Microsoft Learn
- Set-ExecutionPolicy, PowerShell 7.4 | Microsoft Learn
Related tools
- Hash generator: hash the archive before you send it and after it arrives, so a truncated transfer shows up as a different value.
Related guides
- Verify a file with Get-FileHash: the comparison that reports a good file as bad, and why the case of the hex matters.
- Check a checksum on Linux: for the common case where the archive lands on a Linux box and the checksum gets read with the GNU tools instead.
- Upgrade to PowerShell 7 on Windows: the version everything on this page was measured on, and how to get it alongside 5.1.
- The ROBOCOPY command in Windows: when the folder is too big to zip and copying it is the better answer.
Five cheat sheets, one PDF
Subnet masks, PowerShell, Linux commands, HTTP status codes and the ESXi command line - one page each, free to keep. Leave an address and it arrives in a minute.