1

I have been trying to have powershell or batch scripts delete a folder that contains all my scripts when I am finished. Originally I tried Remove-Item -Path "C:\Tool" -Recurse -Force which worked no problem if run as a script for a location outside of C:\Tool. It would complain the files were in use when run from a script within. After some research I found &cmd.exe /c rd /s /q "C:\Tool" which worked much better, but even though I closed the GUI, the command wouldn't delete the img files/folders in use.

Both the above commands run perfectly when initiated from a USB drive.

Previously I created a second script in the temp folder which would delete all the files and then itself. I am looking for a new way to do this to streamline the new design I'm working on. I want the script to work from either within C:\Tool or from a USB drive.

The control flow is such:
1)Script loads all functions
2)GUI is displayed (which contains imgs)
3)Button is pressed
4)GUI is closed
5)Folder containing script is deleted

Step 5 is my issue as already explained. Not all files are removed by either attempted commands and variations of commands.

I want step 5 to work regardless is command is called from a button on the GUI, it autoruns as a part of the script, or a script in another location such as a USB calls it to delete the folder C:\Tool

mklement0
  • 382,024
  • 64
  • 607
  • 775
Gary Allen
  • 140
  • 1
  • 2
  • 15
  • While yes a GUI button triggers the deletion... It has no effect on the problem as the command could be placed anywhere in the script with or without the GUI being displayed. The problem is the script loads the GUI in the background and then runs the delete and doesn't delete everything because of files 'in use' by the powershell script. The `cmd /c rd` command deletes all the script without an issue, but the images used by the GUI are left untouched due to being 'in use'. The `Remove-Item` wont delete anything in the folder because the powershell script is 'in use' – Gary Allen Aug 19 '18 at 20:31
  • 1
    From PowerShell script, you can pass the delete command to CMD prompt at end of your script.`Invoke-Expression -Command "cmd.exe /c del /f C:\Tool* /q"` – Jegan.M Aug 21 '18 at 07:59

1 Answers1

0

We don't know the specifics of how your GUI is displayed, but, assuming you're using a WinForms GUI constructed in PowerShell code, your problem may be how your GUI construction code loads images from files in the folder that you later want to delete.

Notably, if you use something like:

[Bitmap]::new(<file-path>)
[System.Drawing.Image]::FromFile(<file-path>)

the specified file apparently stays open for the remainder of the PowerShell session, and you won't be able to delete your folder.

The solution is to create a new image instance in memory that copies the loaded-from-file image, and to then dispose of the loaded-from-file image, which releases the lock on the underlying file, as demonstrated in this [C#] answer.

Here's a minimal script, demo.ps1, that demonstrates the approach:

  • Save it to its own, temporary, throw-away folder.

  • Copy a small image file named demo.png into the same folder.

  • Then invoke it as <temp-folder>/demo -SelfDestruct to see it in action; the need to specify
    -SelfDescript is a precaution, given that accidental invocation would wipe out the entire folder in which the script lives.

demo.ps1:

param([switch] $SelfDestruct)

# Load the WinForms assembly.
Add-Type -AssemblyName System.Windows.Forms    

# Create the form.
$form = New-Object system.Windows.Forms.Form -Property @{
    ClientSize = New-Object System.Drawing.Point 400,100
    Text       = "Dialog"
}    

# Add a PictureBox control that loads its image from 'demo.png'
$form.Controls.Add((New-Object System.Windows.Forms.PictureBox  -Property @{
Image = & { 
    # Load the image from file in the same folder as a script into
    # a temporary variable.
    $tmpImg = [System.Drawing.Image]::FromFile((Join-Path $PSScriptRoot 'demo.png')) 
    # Create an in-memory copy of the image. 
    New-Object System.Drawing.Bitmap $tmpImg
    # Dispose of the from-file image, which releases the file.
    $tmpImg.Dispose()
}
Location = New-Object System.Drawing.Point 10, 10
}))


# Show the form and wait for the use to close it.
$null = $form.ShowDialog()

if ($SelfDestruct) { # Remove the running script's entire folder.
  if ("$($PWD.Path)\" -like "$PSScriptRoot\*") {                                                      #"
      Push-Location C:\ # must switch to different dir. before deleting.
  }
  # Remove the entire folder.
  Remove-Item -literalpath $PSScriptRoot -Recurse -Force
  exit
}

Here's a variant that triggers the removal by button click, via an event handler:

param([switch] $SelfDestruct)

Add-Type -AssemblyName System.Windows.Forms

function remove-OwnFolder {
    # If the current dir. is in the subtree of the folder to delete, 
    # we must switch to different dir. before deleting.
    if ("$($PWD.Path)\" -like "$PSScriptRoot\*") {                                                      #"
        Push-Location C:\ 
    }
    # Remove the script's parent folder as a whole.
    Remove-Item -literalpath $PSScriptRoot -Recurse -Force
}

# Create the form.
$form = New-Object system.Windows.Forms.Form -Property @{
    ClientSize = New-Object System.Drawing.Point 400,100
    Text       = "Dialog"
}    

# Add a PictureBox control that loads its image from 'demo.png'
$form.Controls.Add((New-Object System.Windows.Forms.PictureBox  -Property @{
    Image = & { 
        # Load the image from file in the same folder as a script into
        # a temporary variable.
        $tmpImg = [System.Drawing.Image]::FromFile((Join-Path $PSScriptRoot 'demo.png')) 
        # Create an in-memory copy of the image. 
        New-Object System.Drawing.Bitmap $tmpImg
        # Dispose of the from-file image, which releases the file.
        $tmpImg.Dispose()
    }
    Location = New-Object System.Drawing.Point 10, 10
}))


# Add a button that will trigger the self-destruction
$btnSelfDestruct = New-Object system.Windows.Forms.Button -Property @{
    Text              = "Submit"
    Location          = New-Object System.Drawing.Point 160, 60
}
$form.Controls.Add($btnSelfDestruct)

# Add the button-click event handler.
$btnSelfDestruct.Add_Click({
    $form.Close()
    if ($SelfDestruct) {
        remove-OwnFolder
    }
    exit
})

# Show the form and wait for the use to close it.
$null = $form.ShowDialog()
mklement0
  • 382,024
  • 64
  • 607
  • 775