This week, I was asked to create a PowerShell script that could find all EFS encrypted files on a Windows 7 computer and decrypt them to prepare the computer to be removed from the domain. As EFS encrypted files are encrypted using a private key that is stored in the user profile, they would otherwise be unreadable when using the new local user profile once the PC was removed from the domain.

Anyways, here is the script:


#Requires -version 3.0

# Unprotect-FilesOnLogicalDisk_v3.ps1
# Version 1.0
# By Marco Janse
# Last modified on Thursday, 3rd of January 2019

# This script will scan for EFS encrypted files and folders on all logical drives and 
# decrypt them using a combination of PowerShell methods and the cipher utility.
# This script requires PowerShell version 3 or higher. For PowerShell version 2, use 
# Unprotect-FilesOnLogicalDisk_v2.ps1, that you can find on https://github.com/MarcoJanse/Scripts
# The script will log every step in a logfile on C:\Logs by default.


# START OF SCRIPT


# Verify the existence of a Logs directory. If it doesn not exist, create it.
If (-not(Test-Path -Path C:\Logs))
    {
        New-Item -Path C: -Name Logs -ItemType directory
    }


# Date and Time
$today = Get-Date
$filename = $today.ToString("yyyy-MM-dd")
$logFile = "C:\Logs\EfsDecryptLog_$filename.log"


# Get al logical drives en put the output in a variabele named $drive
$drive = Get-WmiObject Win32_logicaldisk | Select-Object -ExpandProperty deviceID
Add-Content $logFile "$today Found the following drives: $drive"

# Let the user know the current status of the script
Write-Host "scanning all logical drives for encrypted files, please be patient..."

# Create a variable named $encryptedfiles that contains all items on all logical drives with a 'encrypted' attribute set

$encryptedfiles += foreach ($d in $drive) {
 
                    Get-ChildItem $d -File -Recurse -Force -ErrorAction SilentlyContinue |
                    Where-Object { $_.Attributes -match "Encrypted" } |
                    Select-Object -ExpandProperty FullName
                    }


# We will write to the logfile now log the amount of encrypted files and all the encrypted files with full pathname
Write-Host "Found $($encryptedfiles.count) encrypted files:"
Add-Content $logFile "$today Found $($encryptedfiles.count) encrypted files:"
Add-Content $logFile ""


foreach ($file in $encryptedfiles){
    Add-Content $logFile "$file"
    }

# Next we'll add some extra lines for easy reading the logfile
Add-Content $logFile "==============================================="
Add-Content $logFile "$today total $($encryptedfiles.count) encrypted files"
Add-Content $logfile ""

# Now, we'll start decrypting every file in the $encryptedfiles variable
Write-Host "starting decryption of all found files, please be patient..."

foreach ($file in $encryptedfiles) {
    try
    {
        (Get-Item -Force -LiteralPath $file).Decrypt()
        Add-Content $logFile "$file decrypted"
    }
    catch [Exception]
    {
        Add-Content $logFile "ERROR: Decrypting $file failed. Error message: $_.Exception.ToString()"
    }   
}

# Now we write a completed decrypting files status message to the logfile
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "Finished decrypting files"
Write-Host "Finished decrypting files"

# Next up, we want to remove the encrypted flag from all the folders as well
# We'll start by inventorying the encrypted folders again
Write-Host "scanning all logical drives for encrypted folders, please be patient..."

$encryptedfolders += foreach ($d in $drive) {
 
                    Get-ChildItem $d -Directory -Recurse -Force -ErrorAction SilentlyContinue |
                    Where-Object { $_.Attributes -match "Encrypted" } |
                    Select-Object -ExpandProperty FullName
                    }


# We will write to the logfile now log the amount of encrypted folders and all the encrypted folders with full pathname
Write-Host "Found $($encryptedfolders.count) encrypted folders:"
Add-Content $logFile "$today Found $($encryptedfolders.count) encrypted folders:"
Add-Content $logFile ""


foreach ($folder in $encryptedfolders){
    Add-Content $logFile "$folder"
    }

# Next we'll add some extra lines for easy reading the logfile
Add-Content $logFile "==============================================="
Add-Content $logFile "$today total $($encryptedfolders.count) encrypted folders"
Add-Content $logfile ""

# Now, we'll start decrypting every folder in the $encryptedfolders variable using the cipher utility
Write-Host "starting decryption of all found folders, please be patient..."

foreach ($folder in $encryptedfolders) {
    try
    {
        cipher.exe /d /i $folder
        Add-Content $logFile "$folder decrypted"
    }
    catch [Exception]
    {
        Add-Content $logFile "ERROR: Decrypting $folder failed. Error message: $_.Exception.ToString()"
    }   
}

# Finally, a closing message to the logfile
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "Finished decrypting folders"
Write-Host "Finished decrypting folders"
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "===END of script==="

Write-Host "===End of script==="

# END of Script

Windows includes the cipher utility to encrypt and decrypt files, but I wanted to use native PowerShell as much as possible to log all encrypted and decrypted files to a logfile. To decrypt a file using PowerShell, we can use the decrypt method found in the Get-Item cmdlet.

However, the folders aren’t actually encrypted: folders have a encrypted attribute, that encrypts all files that are being put inside it. I didn’t find an easy way to remove this attribute using PowerShell: other attributes like the archive were no problem, but removing the encrypt attribute seems to be a no-go. As a result, I had to use the cipher executable to actually remove this attribute.

Please note, if you want to run this script on a computer still running PowerShell v2, you will have to use the script below instead. There is only one small modification for running this script on v2, and that is that the Get-ChildItem cmdlet does not contain the -file or -directory switches, so you have to filter between files and folders using the PSIsContainer-property.

Here’s the version 2 script:



#Requires -version 2.0

# Unprotect-FilesOnLogicalDisk_v2.ps1
# Version 1.0
# By Marco Janse
# Last modified on Thursday, 3rd of January 2019

# This script will scan for EFS encrypted files and folders on all logical drives and
# decrypt them using a combination of PowerShell methods and the cipher utility.
# This script is suitable for PowerShell Version 2 or higher.
# The script will log every step in a logfiles on C:\Logs by default.

# START OF SCRIPT

# Verify the existence of a Logs directory. If it doesn not exist, create it.
If (-not(Test-Path -Path C:\Logs))
{
New-Item -Path C: -Name Logs -ItemType directory
}

# Date and Time
$today = Get-Date
$filename = $today.ToString("yyyy-MM-dd")
$logFile = "C:\Logs\EfsDecryptLog_$filename.log"

# Get al logical drives en put the output in a variabele named $drive
$drive = Get-WmiObject Win32_logicaldisk | Select-Object -ExpandProperty deviceID
Add-Content $logFile "$today Found the following drives: $drive"

# Let the user know the current status of the script
Write-Host "scanning all logical drives for encrypted files, please be patient..."

# Create a variable named $encryptedfiles that contains all items on all logical drives with a 'encrypted' attribute set

$encryptedfiles += foreach ($d in $drive) {

Get-ChildItem $d -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { !$_.PSIsContainer } |
Where-Object { $_.Attributes -match "Encrypted" } |
Select-Object -ExpandProperty FullName
}

# We will write to the logfile now log the amount of encrypted files and all the encrypted files with full pathname
Write-Host "Found $($encryptedfiles.count) encrypted files:"
Add-Content $logFile "$today Found $($encryptedfiles.count) encrypted files:"
Add-Content $logFile ""

foreach ($file in $encryptedfiles){
Add-Content $logFile "$file"
}

# Next we'll add some extra lines for easy reading the logfile
Add-Content $logFile "==============================================="
Add-Content $logFile "$today total $($encryptedfiles.count) encrypted files"
Add-Content $logfile ""

# Now, we'll start decrypting every file in the $encryptedfiles variable
Write-Host "starting decryption of all found files, please be patient..."

foreach ($file in $encryptedfiles) {
try
{
(Get-Item -Force -LiteralPath $file).Decrypt()
Add-Content $logFile "$file decrypted"
}
catch [Exception]
{
Add-Content $logFile "ERROR: Decrypting $file failed. Error message: $_.Exception.ToString()"
}
}

# Now we write a completed decrypting files status message to the logfile
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "Finished decrypting files"
Write-Host "Finished decrypting files"

# Next up, we want to remove the encrypted flag from all the folders as well
# We'll start by inventorying the encrypted folders again
Write-Host "scanning all logical drives for encrypted folders, please be patient..."

$encryptedfolders += foreach ($d in $drive) {

Get-ChildItem $d -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer } |
Where-Object { $_.Attributes -match "Encrypted" } |
Select-Object -ExpandProperty FullName
}

# We will write to the logfile now log the amount of encrypted folders and all the encrypted folders with full pathname
Write-Host "Found $($encryptedfolders.count) encrypted folders:"
Add-Content $logFile "$today Found $($encryptedfolders.count) encrypted folders:"
Add-Content $logFile ""

foreach ($folder in $encryptedfolders){
Add-Content $logFile "$folder"
}

# Next we'll add some extra lines for easy reading the logfile
Add-Content $logFile "==============================================="
Add-Content $logFile "$today total $($encryptedfolders.count) encrypted folders"
Add-Content $logfile ""

# Now, we'll start decrypting every folder in the $encryptedfolders variable using the cipher utility
Write-Host "starting decryption of all found folders, please be patient..."

foreach ($folder in $encryptedfolders) {
try
{
cipher.exe /d /i $folder
Add-Content $logFile "$folder decrypted"
}
catch [Exception]
{
Add-Content $logFile "ERROR: Decrypting $folder failed. Error message: $_.Exception.ToString()"
}
}

# Finally, a closing message to the logfile
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "Finished decrypting folders"
Write-Host "Finished decrypting folders"
Add-Content $logfile ""
Add-Content $logfile ""
Add-Content $logFile "===END of script==="

Write-Host "===End of script==="

# END of Script

Both scripts can be found in my GitHub repository: https://github.com/MarcoJanse/Scripts/tree/master/FileSystem

  1. I somehow encrypted some files without meaning to, and because of it I can’t backup my user folder to Mozy. Is there a way to remove the encryption from all the files or at least find out which one are encrypted?

  2. I somehow encrypted some files without meaning to, and because of it I can’t backup my user folder to Mozy. Is there a way to remove the encryption from all the files or at least find out which one are encrypted?

  3. You can use my script to quickly find all the encrypted files by just using this part of the script as a oneliner:

    Get-ChildItem -path ‘C:\’ -File -Recurse -Force -ErrorAction SilentlyContinue |
    Where-Object { $_.Attributes -match “Encrypted” } |
    Select-Object -ExpandProperty FullName

    You should change the path to the drive or folder that you want to start your scan

  4. Hi,
    I am trying to follow ur script but not able to as I am not educated in this.Can you tell me which are the commands I need to use to decrypt the files without any other dialogues.

    • Hi Munira,

      If you have only a couple of files or folders that need decrypting, you might be better off using the GUI. If not, please provide me with some more details of the parts your’re struggling with.

  5. Hi there,
    I only have 1 encrypted file that I encrypted years ago and I can’t even remember through which program I encrypted it. Now I can’t rename, delete or remove it,. Do you have a solution to my problem sir?

  6. Thanks Marco, saved my bacon with this script! Had written countless hours of my own trying to do just this, but couldn’t get it to recurse the different drives.

  7. Hi Munira

    Has this issue been resolved?

    @ admin, can you provide some Powershell scripts on how to decrypt .msg files on a server or a specific location on a server?

    Kind Regards

    Nivan

  8. This script is great – thanks for taking the time and sharing it. I have discovered a few bugs though.

    $logFile = "C:LogsEfsDecryptLog_$filename.log"
    Does not expand to a reasonable path – in my case it ends up dumping the log in C:\Windows\System32. Adding the path separators solves this:
    $logFile = $("C:\Logs\" + $filename + ".log")

    The second one is more severe – it seems the file name expansion is a bit sensitive, causing the script to fail on filenames with brackets – [ and ]. For example, these will throw “File Not Found”:

    archive_sync_sftp 2018-05-07 101702.491 [Stopped].log
    archive_sync_sftp 2018-05-10 105037.639 [Error].log

    It also fails with the hidden “.suo” file that Visual Studio generates. I’m pretty sure both cases will be solved by escaping or quoting the filename expansion somehow, but not sure how to go about it.

    Eg. I assume the problem is with (Get-Item $file).Decrypt() but I’m not sure how to make it more robust. Any ideas?

    Thanks!

  9. As of this moment, 3/14/19 14:40EST, the PowerShell 2 version of this script works perfectly in a Windows 7 Pro/Server 2008R2 AD environment.

    Woo-hooo!

    I did modify the drive selection to focus it on the only mapped drive that was giving us problems, by changing

    $drive = Get-WmiObject Win32_logicaldisk | Select-Object -ExpandProperty deviceID

    to:

    $drive = Get-WmiObject Win32_logicaldisk -Filter “DeviceID=’Q:'” | Select-Object -ExpandProperty deviceID

    , and in testing I learned you can focus on local drives only by:

    $drive = Get-WmiObject Win32_logicaldisk -Filter “drivetype=3” | Select-Object -ExpandProperty deviceID

    THANK YOU, Marco, for saving me hours of mindless drudge work!!! I hope you don’t mind I’m going to share this.

Leave a Reply

Your email address will not be published. Required fields are marked *