Skip to content

Instantly share code, notes, and snippets.

@mmdemirbas
Created March 23, 2013 20:54
Show Gist options
  • Save mmdemirbas/5229315 to your computer and use it in GitHub Desktop.
Save mmdemirbas/5229315 to your computer and use it in GitHub Desktop.
PowerShell script to set or clear NTFS read-only flag of a volume by volume label
#########################################################################
# #
# Script to set or clear read-only flag of an NTFS volume. #
# #
# Usage: .\set-ntfs-ro.ps1 set "MY DISK LABEL" #
# .\set-ntfs-ro.ps1 clear "MY DISK LABEL" #
# #
# Author: Muhammed Demirbas, mmdemirbas at gmail dot com #
# Date : 2013-03-23 #
# #
#########################################################################
param($setOrClear, $diskLabel)
if( [string]::IsNullOrWhiteSpace($setOrClear) )
{
$ScriptName = $MyInvocation.MyCommand.Name
"usage: .\$ScriptName set ""MY DISK LABEL"""
" .\$ScriptName clear ""MY DISK LABEL"""
return
}
if( $setOrClear -ne "set" -and $setOrClear -ne "clear" )
{
throw 'Valid actions are "set" and "clear"!'
}
if( [string]::IsNullOrWhiteSpace($diskLabel) )
{
throw "Please specify a non-blank disk label!"
}
# Path of the temporary file to use as diskpart script
$scriptFile = "$env:TMP\set-ntfs-ro-script.tmp"
# Save "list volume" command to a temp-file
"list volume" | Out-File -Encoding ascii $scriptFile
# Execute diskpart providing the script, and select the involved line
$matches = diskpart /s $scriptFile | Select-String $diskLabel
if( $matches.Length -eq 0 )
{
throw "No match for the label: $diskLabel"
}
elseif ( $matches.Length -ge 2 )
{
throw "More than one match for the label: $diskLabel"
}
# Obtain volume number
$words = $matches.Line.Trim().Split(" ")
if( !$words -or $words.Length -le 1 )
{
throw "Volume number couldn't be obtained for the volume:`n$line"
}
$volumeNum = $words.Get(1)
# Save the command to modify read-only flag to a temp-file
"select volume $volumeNum
att vol $setOrClear readonly
detail vol" | Out-File -Encoding ascii $scriptFile
# Execute the command, and print details
diskpart /s $scriptFile
# Clean the waste
del $scriptFile
@lxandr
Copy link

lxandr commented Oct 10, 2019

Hi! Nice script! I've digged into it and surprisingly came to even simpler solution:

$scriptVolumeLabel = "Music"
$scriptDriveLetter = ( Get-Volume -FileSystemLabel $scriptVolumeLabel ).DriveLetter

if ([string]::IsNullOrEmpty($scriptDriveLetter))
{
	echo "Volume '$scriptVolumeLabel' was not found!"
	echo ""
	pause
	exit
}
 Write-VolumeCache -DriveLetter $scriptDriveLetter
 Set-Partition -DriveLetter $scriptDriveLetter -IsReadOnly $true

as you can see there is no need in "diskpart" now! Cool!
To enable write, just change two last lines:

# we don't need this here, because volume is read-only:
# Write-VolumeCache -DriveLetter $scriptDriveLetter
Set-Partition -DriveLetter $scriptDriveLetter -IsReadOnly $false

I've tested this script on my usb harddrive (fixed disk) with GPT(!) NTFS partition and it works. But, it DOESN'T work on usb removable drives as flash drives - you'll get an error from "Set-Partition"!

Also, about read-only usb flash drives. My test shows, that it is plausible... But:

  1. you can't set usb flash drive to read-only nor by "powershell" neither by "diskpart" by "attributes volume set readonly" - you'll get an error.
  2. to set drive to read-only it MUST be formatted as NTFS
  3. your usb flash drive MUST have GPT!
  4. it looks like setting an "read-only" attribute to a GPT NTFS volume just changes GPT's "read-only" attribute of the volume! So THAT's the key! And this can be easily achived through "diskpart": GPT ATTRIBUTES=0x1000000000000000!

To automate this, I wrote 3 scripts (they work only with usb removable drives! also, they auto-elevate themselves through UAC):

mk-USB-GPT-NTFS.ps1

# How to Run PowerShell Scripts with Administrative Privileges 
# https://www.petri.com/run-powershell-scripts-with-administrative-privileges

param([switch]$Elevated)

function Check-Admin
{
	$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
	$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

if ((Check-Admin) -eq $false)
{
	if ($elevated)
	{
		# could not elevate, quit
	}
		else
	{
		Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -ExecutionPolicy Bypass -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
	}
	
	exit
}


Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"}

echo ""
$USBDrive = Read-Host -Prompt 'Enter USB disk number (or press "Enter" for exit)'
if ([string]::IsNullOrEmpty($USBDrive)) { exit }

# select only non-system and USB drives
$USBDrive = Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"} | Where Number -eq $USBDrive

if (! ([string]::IsNullOrEmpty($USBDrive)) )
{
	echo ""
	echo $USBDrive
	echo ""
	
	$Answer = Read-Host -Prompt "Going to DELETE all partitions from this USB-drive and convert it to GPT with NTFS partition! Continue? (type 'Y')"
	echo ""

	if ("$Answer" -ceq "Y")
	{
		# "Initialize-Disk : The disk has already been initialized."
		# https://social.technet.microsoft.com/Forums/en-US/1b1f2053-4e47-4d20-941a-d45ec9116dbe/initializedisk-the-disk-has-already-been-initialized?forum=winserversetup
		# https://social.technet.microsoft.com/Forums/en-US/8e5916ef-60c1-4dac-9de2-18bd2984a0a5/initializedisk-the-disk-has-already-been-initialized?forum=winserverpowershell
		# https://serverfault.com/questions/494848/cannot-initialize-disk-in-powershell-initialize-disk-throws-the-disk-has-alre/530679
		# 
		# and only this helped:
		# https://www.thomasmaurer.ch/2018/07/create-a-usb-drive-for-windows-server-2019-installation/
		# "Set-Disk -PartitionStyle GPT", NOT "Initialize-Disk"!
		# looks like "Initialize-Disk" doesn't work with USB/removable drives!
		
		$USBDrive | Clear-Disk -RemoveData -RemoveOEM -Confirm:$false -PassThru
		$USBDrive | Set-Disk -PartitionStyle GPT
		$Volume = $USBDrive | New-Partition -UseMaximumSize -AssignDriveLetter | Format-Volume -FileSystem NTFS -Confirm:$false 
	}

	echo ""
	pause

}

USB_GPT_set_read-only.ps1

# https://docs.microsoft.com/en-us/powershell/module/storage/get-disk?view=win10-ps

# How to Run PowerShell Scripts with Administrative Privileges 
# https://www.petri.com/run-powershell-scripts-with-administrative-privileges

param([switch]$Elevated)

function Check-Admin
{
	$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
	$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

if ((Check-Admin) -eq $false)
{
	if ($elevated)
	{
		# could not elevate, quit
	}
		else
	{
		Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -ExecutionPolicy Bypass -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
	}
	
	exit
}


Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"}

echo ""
$USBDriveNumber = Read-Host -Prompt 'Enter USB disk number (or press "Enter" for exit)'

if ([string]::IsNullOrEmpty($USBDriveNumber)) { exit }

# select only non-system and USB drives
$USBDrive = Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"} | Where Number -eq $USBDriveNumber

if (! ([string]::IsNullOrEmpty($USBDrive)) )
{
	# WARNING! there is NO check for partitions count > 1 on USB drives!
	
	$DiskPartScriptFile = "$env:TMP\diskpart_USB_GPT_ATTRS.tmp"

	$Letter = Get-Partition $USBDriveNumber | Select -ExpandProperty DriveLetter
	echo ""
	echo "Extra safety - flushing cache of volume '$Letter' ..."
	Write-VolumeCache $Letter
	echo ""

	#echo "$USBDrive"
	# "diskpart attributes volume set readonly" doesn't work on removable drives!
	# so, we gonna do some magic - set "readonly" not by "volume attribute" but by GPT attribute :)
	
	# GPT ATTRIBUTES=0x8000000000000000 is the default
	# GPT ATTRIBUTES=0x1000000000000000 is "read-only"
	
"select disk $USBDriveNumber
select part 1
remove all dismount
GPT ATTRIBUTES=0x1000000000000000
assign" | Out-File -Encoding ascii $DiskPartScriptFile

	# Execute the command, and print details
	echo "diskpart /s $DiskPartScriptFile"
	echo ""
	Get-Content $DiskPartScriptFile
	echo ""
	diskpart /s $DiskPartScriptFile
	del $DiskPartScriptFile
	echo ""
	pause

}

USB_GPT_enable_write.ps1

# https://docs.microsoft.com/en-us/powershell/module/storage/get-disk?view=win10-ps

# How to Run PowerShell Scripts with Administrative Privileges 
# https://www.petri.com/run-powershell-scripts-with-administrative-privileges

param([switch]$Elevated)

function Check-Admin
{
	$currentUser = New-Object Security.Principal.WindowsPrincipal $([Security.Principal.WindowsIdentity]::GetCurrent())
	$currentUser.IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
}

if ((Check-Admin) -eq $false)
{
	if ($elevated)
	{
		# could not elevate, quit
	}
		else
	{
		Start-Process powershell.exe -Verb RunAs -ArgumentList ('-noprofile -ExecutionPolicy Bypass -file "{0}" -elevated' -f ($myinvocation.MyCommand.Definition))
	}
	
	exit
}


Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"}

echo ""
$USBDriveNumber = Read-Host -Prompt 'Enter USB disk number (or press "Enter" for exit)'

if ([string]::IsNullOrEmpty($USBDriveNumber)) { exit }

# select only non-system and USB drives
$USBDrive = Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"} | Where Number -eq $USBDriveNumber

if (! ([string]::IsNullOrEmpty($USBDrive)) )
{
	# WARNING! there is NO check for partitions count > 1 on USB drives!
	
	$DiskPartScriptFile = "$env:TMP\diskpart_USB_GPT_ATTRS.tmp"

	# we don't need this here, because drive should be write-protected any way - nothing to flush :)
	#$Letter = Get-Partition $USBDriveNumber | Select -ExpandProperty DriveLetter
	#echo ""
	#echo "Extra safety - flushing cache of volume '$Letter' ..."
	#Write-VolumeCache $Letter
	echo ""

	#echo "$USBDrive"
	# "diskpart attributes volume set readonly" doesn't work on removable drives!
	# so, we gonna do some magic - set "readonly" not by "volume attribute" but by GPT attribute :)
	
	# GPT ATTRIBUTES=0x8000000000000000 is the default
	# GPT ATTRIBUTES=0x1000000000000000 is "read-only"
	
"select disk $USBDriveNumber
select part 1
remove all dismount
GPT ATTRIBUTES=0x8000000000000000
assign" | Out-File -Encoding ascii $DiskPartScriptFile

	# Execute the command, and print details
	echo "diskpart /s $DiskPartScriptFile"
	echo ""
	Get-Content $DiskPartScriptFile
	echo ""
	diskpart /s $DiskPartScriptFile
	del $DiskPartScriptFile
	echo ""
	pause

}

Also forgot to mention:

  1. with GPT NTFS partition style, you'll also get read-only usb removable and fixed drives cross systems! Cool! (I've checked this on two different computers with Windows 8.1).
  2. to make sure that GPT "read-only" attribute is applied, script dismounts volume before that (so, you don't have to replug the device). So, make sure, you closed all your opened files from the drive before launching the script!
  3. after applying "read-only" attribute you can run into problem with safely removal of the drive - "device is busy". I think, that this is because of cached write data: drive is set to read-only (modified data wasn't flush in time), we try to eject device, OS tries to flush cached data, but the drive is already "read-only", so we get "device is busy" error. To minimize this, script tries to flush data right before sets volume to read-only.

@backbone10
Copy link

Hi !
@lxandr : Can you please explain the usage and function of the first skript (mk-USB-GPT-NTFS.ps1).
Unfortunately I dont understand it and it seems a little bit risky for me to try without understanding . THANKS !!
BR BB10

@lxandr
Copy link

lxandr commented Dec 28, 2019

@backbone10 sure! (strange, but your comment appeared here only now - months later, and so I didn't see it before, sorry).
First function "Check-Admin" checks if the script is run with elevated privileges. And if it's not, it tries to re-run itself with elevated privileges through UAC (elevation is needed to work with disk devices). If we remove this part from script, we must run this script with elevated privileges every time by hand. So this part of code just simplifies things - it's simpler to run script from user without administrative privileges - UAC window automatically appears and asks for user and password to elevate privileges.

Here we get a list of drives connected via usb (filtering all disks except usb) and print them:
Get-Disk | Where-Object IsSystem -eq $False | Where-Object -FilterScript {$_.BusType -Eq "USB"}

Then, we ask user to enter disk number from this list (in case more than one usb drive is connected):
$USBDrive = Read-Host -Prompt 'Enter USB disk number (or press "Enter" for exit)'

Then we make extra safety checks, and ask user if he sure to continue with deleting all partitions from that usb disk (Clear-Disk) and Creating GPT-style NTFS partition on it:
Set-Disk -PartitionStyle GPT, New-Partition ..., Format-Volume -FileSystem NTFS
I don’t know how this script will behave on windows 7 system - because of older default windows 7's PowerShell version.
I tested these scripts only on windows 8.1 64 bit. (so, on windows 10 they should work too).
It's better to test things under VirtualBox.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment