James Collins
James Collins

Reputation: 9

C# Check if pendrive is read-only and run DISKPART to change readonly status

I'm quite lost with this task. Basically I have to check if a pendrive has readonly property.

This is my code:

private void protectpendrive(string disk)
        {
            Process p = new Process();
            p.StartInfo.UseShellExecute = false;   
            p.StartInfo.RedirectStandardOutput = true; 
            p.StartInfo.FileName = @"C:\Windows\System32\diskpart.exe";   
            p.StartInfo.RedirectStandardInput = true;                    
            p.Start();
            p.StandardInput.WriteLine("select volume " + disk);
            p.StandardInput.WriteLine("attributes disk set readonly");
            debugListView.Items.Add(DateTime.Now + "Pendrive is now read-only.");
            p.StandardInput.WriteLine("exit");
        }

To unprotect:

private void unprotectpendrive(string disk)
        {
            Process p = new Process();
            p.StartInfo.UseShellExecute = false;   
            p.StartInfo.RedirectStandardOutput = true; 
            p.StartInfo.FileName = @"C:\Windows\System32\diskpart.exe";   
            p.StartInfo.RedirectStandardInput = true;                    
            p.Start();
            p.StandardInput.WriteLine("select volume " + disk);
            p.StandardInput.WriteLine("attributes disk clear readonly");
            debugListView.Items.Add(DateTime.Now + "Pendrive is now not protected.");
            p.StandardInput.WriteLine("exit");
        }

This is my control for the current status, but it doesn't work either:

        private bool checkreadonly(string disk)
        {
            DirectoryInfo pendrive = new DirectoryInfo(disk);
            if (pendrive.Attributes.HasFlag(FileAttributes.ReadOnly))
            {
                debugListView.Items.Add(DateTime.Now + " Pendrive is read-only.");
                return true;
            }
            debugListView.Items.Add(DateTime.Now + " Pendrive is writable.");
            return false;
        }

The issues I have are:

  1. Diskpart requires administrator priviledges, I tried with StartInfo.Verb="runas" but it seems it's not taken into consideration
  2. If I launch my app with administrator rights, the diskpart window opens but it doesn't close at the end. Overall the status doesn't change.

What am I doing wrong?

Upvotes: 0

Views: 286

Answers (1)

It all makes cents
It all makes cents

Reputation: 4993

The following shows how to both set and clear the readonly attribute for a removable USB disk.

Create a new Windows Forms App (.NET Framework) project (name: ProcessDiskPartTest)

Add an Application Manifest to your project

Note: This is used to prompt the user to execute the program as Administrator.

  • In VS menu, click Project
  • Select Add New Item...
  • Select Application Manifest File (Windows Only) (name: app.manifest)
  • Click Add

In app.manifest, replace

<requestedExecutionLevel level="asInvoker" uiAccess="false" />

with

<requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />

Option 1 - Diskpart (script)

The following shows how to create a diskpart script and then use System.Diagnostics.Process to execute the script.

Add the following using directives:

  • using System.IO;
  • using System.Diagnostics;

The following code will use Process to execute a diskpart script.

public void RunDiskPartScript(string scriptText)
{
    string diskpartPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "System32", "diskpart.exe");

    if (!System.IO.File.Exists(diskpartPath))
        throw new Exception(String.Format("'{0}' doesn't exist.", diskpartPath));

    Debug.WriteLine("diskpartPath: " + diskpartPath);

    ProcessStartInfo psInfo = new ProcessStartInfo(diskpartPath);

    string scriptFilename = System.IO.Path.Combine(Path.GetTempPath(), Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location));
    Debug.WriteLine($"scriptFilename: '{scriptFilename}'");

    //save script
    System.IO.File.WriteAllText(scriptFilename, scriptText);

    psInfo.Arguments = $"/s {scriptFilename}";

    psInfo.CreateNoWindow = true;
    psInfo.RedirectStandardError = true; //redirect standard Error
    psInfo.RedirectStandardOutput = true; //redirect standard output
    psInfo.RedirectStandardInput = false;
    psInfo.UseShellExecute = false; //if True, uses 'ShellExecute'; if false, uses 'CreateProcess'
    psInfo.Verb = "runas"; //use elevated permissions
    psInfo.WindowStyle = ProcessWindowStyle.Hidden;

    //create new instance and set properties
    using (Process p = new Process() { EnableRaisingEvents = true, StartInfo = psInfo })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        //waits until the process is finished before continuing
        p.WaitForExit();

        if (File.Exists(scriptFilename))
            File.Delete(scriptFilename); //delete file
    }
}

public void SetDiskProperty(string driveLetter, bool isReadOnly)
{
    //create diskpart script text
    StringBuilder sbScript = new StringBuilder();
    sbScript.AppendLine($"select volume {driveLetter}");

    if (isReadOnly)
        sbScript.AppendLine($"attributes disk set readonly");
    else
        sbScript.AppendLine($"attributes disk clear readonly");

    //Debug.WriteLine($"Script:\n'{sbScript.ToString()}'");

    //execute script
    RunDiskPartScript(sbScript.ToString());
}

Usage (Clear Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
SetDiskProperty("H:\", false); 

Usage (Set Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
 SetDiskProperty("H", true);

Option 2 - Diskpart (StandardInput)

The following shows how to call diskpart by using System.Diagnostics.Process and sending commands via StandardInput

Add the following using directives:

  • using System.IO;
  • using System.Diagnostics;
public static void DiskClearReadOnly(string driveLetter)
{
    List<string> commands = new List<string>() { $"select volume {driveLetter}", "attributes disk clear readonly", "exit" };
    RunDiskPart(commands);

    Debug.WriteLine($"Disk '{driveLetter}' is now read-write.");
}

public static void DiskSetReadOnly(string driveLetter)
{
    List<string> commands = new List<string>() { $"select volume {driveLetter}", "attributes disk set readonly", "exit" };
    RunDiskPart(commands);

    Debug.WriteLine($"Disk '{driveLetter}' is now read-only.");
}


private void RunDiskPart(List<string> commands, string arguments = null)
{
    string diskpartPath = Path.Combine(Environment.GetEnvironmentVariable("windir"), "System32", "diskpart.exe");

    if (!System.IO.File.Exists(diskpartPath))
        throw new Exception(String.Format("'{0}' doesn't exist.", diskpartPath));

    Debug.WriteLine("diskpartPath: " + diskpartPath);

    ProcessStartInfo psInfo = new ProcessStartInfo(diskpartPath);

    if (!String.IsNullOrEmpty (arguments))
        psInfo.Arguments = arguments;

    psInfo.CreateNoWindow = true;
    psInfo.RedirectStandardError = true; //redirect standard Error
    psInfo.RedirectStandardOutput = true; //redirect standard output
    psInfo.RedirectStandardInput = true;
    psInfo.UseShellExecute = false; //if True, uses 'ShellExecute'; if false, uses 'CreateProcess'
    psInfo.Verb = "runas"; //use elevated permissions
    psInfo.WindowStyle = ProcessWindowStyle.Hidden;

    //create new instance and set properties
    using (Process p = new Process() { EnableRaisingEvents = true, StartInfo = psInfo })
    {
        //subscribe to event and add event handler code
        p.ErrorDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code 
                Debug.WriteLine("Error: " + e.Data);
            }
        };

        //subscribe to event and add event handler code
        p.OutputDataReceived += (sender, e) =>
        {
            if (!String.IsNullOrEmpty(e.Data))
            {
                //ToDo: add desired code
                Debug.WriteLine("Output: " + e.Data);
            }
        };

        p.Start(); //start

        p.BeginErrorReadLine(); //begin async reading for standard error
        p.BeginOutputReadLine(); //begin async reading for standard output

        //send input
        foreach (string cmd in commands)
        {
            p.StandardInput.WriteLine(cmd);
        }

        //waits until the process is finished before continuing
        p.WaitForExit();
    }
}

Usage (Clear Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
DiskClearReadOnly("H:\"); 

Usage (Set Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
DiskSetReadOnly("H:\");

Option 3 - PowerShell:

The following shows how to use PowerShell to set or clear the readonly attribute for a removable USB drive.

Download/install the appropriate PowerShell NuGet package:

For .NET Framework, choose one of the following:

For .NET (version >= 6), choose one of the following:

See Choosing the right PowerShell NuGet package for your .NET project for more information and Differences between Windows PowerShell 5.1 and PowerShell 7.x

Add the following using directives:

  • using System.Management.Automation.Runspaces
  • using System.Management.Automation;
  • using System.Diagnostics;
public static void DiskClearReadOnly(string driveLetter)
{
    RunPowerShellCommand(driveLetter, false);
    Debug.WriteLine($"Disk '{driveLetter}' is now read-write.");
}

public static void DiskClearReadOnly(int driveNumber)
{
    RunPowerShellCommand(driveNumber, false);
    Debug.WriteLine($"Disk '{driveNumber}' is now read-write.");

}
public static void DiskSetReadOnly(string driveLetter)
{
    RunPowerShellCommand(driveLetter, true);
    Debug.WriteLine($"Disk '{driveLetter}' is now read-only.");
}

public static void DiskSetReadOnly(int driveNumber)
{
    RunPowerShellCommand(driveNumber, true);
    Debug.WriteLine($"Disk '{driveNumber}' is now read-only.");
}

private static void RunPowerShellCommand(string driveLetter, bool isReadOnly)
{
    InitialSessionState iss = InitialSessionState.CreateDefault();
    iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; //set execution policy

    using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
    {
        //open
        runspace.Open();

        using (PowerShell ps = PowerShell.Create())
        {
            ps.Runspace = runspace;

            var psInstance = PowerShell.Create();

            //Get-Partition -DriveLetter <driveLetter>
            psInstance.AddCommand("Get-Partition").AddParameter("DriveLetter", driveLetter);

            //Get-Disk
            psInstance.AddCommand("Get-Disk");

            //Set-Disk -IsReadOnly [True | False]
            psInstance.AddCommand("Set-Disk").AddParameter("IsReadOnly", isReadOnly);

            System.Collections.ObjectModel.Collection<PSObject> outputCollection = psInstance.Invoke();

            //'Set-Disk' doesn't produce any output, so there won't be any output
            foreach (PSObject outputItem in outputCollection)
            {
                //create reference
                string data = outputItem.BaseObject?.ToString();

                System.Diagnostics.Debug.WriteLine(outputItem.ToString());
            }
        }
    }
}

private static void RunPowerShellCommand(int diskNumber, bool isReadOnly)
{
    InitialSessionState iss = InitialSessionState.CreateDefault();
    iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.RemoteSigned; //set execution policy

    using (Runspace runspace = RunspaceFactory.CreateRunspace(iss))
    {
        //open
        runspace.Open();

        using (PowerShell ps = PowerShell.Create())
        {
            ps.Runspace = runspace;

            var psInstance = PowerShell.Create();

            //Get-Disk -Number <diskNumber>
            psInstance.AddCommand("Get-Disk").AddParameter("Number", diskNumber);

            //Set-Disk -IsReadOnly [True | False]
            psInstance.AddCommand("Set-Disk").AddParameter("IsReadOnly", isReadOnly);

            System.Collections.ObjectModel.Collection<PSObject> outputCollection = psInstance.Invoke();

            //'Set-Disk' doesn't produce any output, so there won't be any output
            foreach (PSObject outputItem in outputCollection)
            {
                //create reference
                string data = outputItem.BaseObject?.ToString();

                System.Diagnostics.Debug.WriteLine(outputItem.ToString());
            }
        }
    }
}

Usage (Clear Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
DiskClearReadOnly("H:\"); 

Usage (Set Readonly):

 //The drive letter can be one of the following formats: `H`, `H:`, or `H:\`
DiskSetReadOnly("H:\");

Resources:

Upvotes: 0

Related Questions