Error unmounting an image - powershell

I'm having a problem when unmounting an image:
Dismount-WindowsImage : The specified image is invalid.
Unmount the image or clean up the Vhd and then try again.
A day ago, I broke something, and the script stopped working. Using a script I importing a virtual machine. I mount the image, add the data to the image, unmount it and run. Error occurs when unmounting:
#PS C:\build-grom-git> .\_start-VM.ps1 -name_of_image "w7" -installer "TGTeDataAVCS+ADPonly" -path_to_vmcx "C:\imageOS\win7x64\w7.vhdx\Virtual Machines\47A23F4B-A8EB-4AAC-A745-4AC28FB66FAE.vmcx"
Param(
[Parameter(Mandatory=$True,Position=1)]
[string]$name_of_image, # path to image of virtual machine
[Parameter(Mandatory=$True,Position=2)]
[string]$installer, # name of the folder with install-file
[Parameter(Mandatory=$True,Position=3)]
[string]$path_to_vmcx # path to vmcx file (file for import VM)
)
# static variables
$vhdx_path="Temp\vhdx" # path to image of virtual machine
$mount_dir="Temp\mount_folder" # folder of mounting image
# Run As Administrator
if (!([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { Start-Process powershell.exe "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs; exit }
# 1. Creating folder
# create temporary folders in local machine
New-Item -ItemType directory -Path $vhdx_path
# 2. Work with vhdx files
#copying image to temporary folder in local machine
Import-VM -Path "$path_to_vmcx" -Copy -VhdDestinationPath "Temp\vhdx" -VirtualMachinePath "Temp\vhdx"
# mounting image
MD $mount_dir -Force
Mount-WindowsImage -ImagePath "$vhdx_path\$name_of_image.vhdx" -Path $mount_dir -Index 1
# copying files in the image (virtual machine)
Copy-Item -Path "C:\keys" -Destination "$mount_dir\keys" -Recurse -force # keys for TGTe-Data
Copy-Item -Path "Temp\$installer" -Destination "$mount_dir\Temp\$installer" -Recurse -force # installer for TGTe-Data
Copy-Item -Path "Temp\DB" -Destination "$mount_dir\DB" -Recurse -force # copy database
# unmount image
Dismount-WindowsImage -Path $mount_dir -Save
RD $mount_dir -Force
#3. Run VM
Start-VM $name_of_image
# enough time to run
Start-Sleep -s 30
How can I correct the error with unmount?

I could not fix it, but I found another solution. Sorry, but i can't correctly added my code. Stack has very stupid design.
# mount the vhdx image
$MountedDisk = Mount-VHD -Path "Virtual Hard Disks\windows7x64.vhdx" -Passthru
# get count of the disk in image (i have got 2 disk in image: system-disk and work-disk)
$diskNo=$MountedDisk.DiskNumber
# get letters for this disks
$driveLetter=(Get-Disk $diskNo | Get-Partition).DriveLetter
# Creating path to need disk (i need work-disk)
$path_to_mount_vhdx = $driveLetter[1] + ":"
# Copying files into work-disk
Copy-Item -Path "C:\keys" -Destination "$path_to_mount_vhdx\keys" -Recurse -force
# unmount
Dismount-VHD $MountedDisk.Path

Related

How to run multiple scripts using text files full of references

I have a script that I feel that I am close to being ready to run but need some help fine tuning things.
My primary objective is this:
From each text file (named after the computer it was generated from), run each script using the data that exists within the .txt file. Each file is output from the C:\Users folder on the computer, listing each user profile that exists on that machine. I need to be able to run the script so that it deletes the specified folders/files for each user profile on that machine.
# Name: CacheCleanup
# Description: Deletes cache files per user on each computer
# Syntax: .\CacheCleanup.ps1
# Author: Nicholas Nedrow
# Created: 06/15/2021
#Text file contains list of all machines that have recently pinged and are online
$Computers = Get-Content "C:\Temp\CacheCleanUp\ComputerUp.txt"
#Users are listed in individual text files assigned with the name of their PC.
$Users = Get-Content "C:\Temp\CacheCleanUp\Computer Users\*.txt"
#Base path for deletion paths
$Path = "\\$PC\c$\users\$user\appdata\local"
#Delete User\Temp files
Remote-Item -Path "$Path\temp\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Teams files
Remove-Item -Path "$Path\Microsoft\Teams" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-ITem -Path "$Path\Microosft\TeamsMeetingAddin" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Microsoft\TeamsPresenceAddin" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Chrome Cache
Remove-Item -Path "$Path\Google\Chrome\User Data\Default\Cache\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete IE Cache
Remove-Item -Path "$Path\Microsoft\Windows\INetCache\*" -Recurse -Force -EA SilentlyContinue -Verbose
#Delete Firefox cache
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\*.*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\cache2\entries\*.*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\thumbnails\*" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\cookies.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\webappstore.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
Remove-Item -Path "$Path\Mozilla\Firefox\Profiles\*.default\cache\chromeapstore.sqlite" -Recurse -Force -EA SilentlyContinue -Verbose
#How to Run each script for each user on each machine
#How to generate detailed log with results of deletion for each section
I will state right away that I am still learning scripting and am unfamiliar with functions, even though I am pretty sure that is what I need to develop here. This is a domain network so the appropriate path for the computer name has been taken into consideration. Each script does run independently, with the computer name specified but I run into issues when it comes to trying to call out each user profile on that computer.
If possible, it would be nice to have some sort of generated report with the outcome of each user profile and what was ran successfully. I don't need to necessarily know every file that was deleted but maybe a list of those files that were unable to be deleted due to conflicts with running programs or permission issues.
You need to use loops. Consider the following code:
$configFiles = "C:\Temp\CacheCleanUp";
Get-Content "$configFiles\TESTComputers.txt" | % {
$PC = $_;
Write-Host "Attempting to clean cache on computer: $PC";
Get-Content "$configFiles\TESTusers.txt" | % {
$user = $_;
$Path = "\\$PC\c$\users\$user\appdata\local"
Write-Host "`tCleaning $Path"
<# Your code goes here #>
}
}
TESTusers.txt contains:
dave
bob
amy
TESTComputers.txt contains:
10.0.0.1
10.0.0.2
10.0.0.3
10.0.0.4
10.0.0.5
This is the output of the above code and computer/user files:
Attempting to clean cache on computer: 10.0.0.1
Cleaning \\10.0.0.1\c$\users\dave\appdata\local
Cleaning \\10.0.0.1\c$\users\bob\appdata\local
Cleaning \\10.0.0.1\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.2
Cleaning \\10.0.0.2\c$\users\dave\appdata\local
Cleaning \\10.0.0.2\c$\users\bob\appdata\local
Cleaning \\10.0.0.2\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.3
Cleaning \\10.0.0.3\c$\users\dave\appdata\local
Cleaning \\10.0.0.3\c$\users\bob\appdata\local
Cleaning \\10.0.0.3\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.4
Cleaning \\10.0.0.4\c$\users\dave\appdata\local
Cleaning \\10.0.0.4\c$\users\bob\appdata\local
Cleaning \\10.0.0.4\c$\users\amy\appdata\local
Attempting to clean cache on computer: 10.0.0.5
Cleaning \\10.0.0.5\c$\users\dave\appdata\local
Cleaning \\10.0.0.5\c$\users\bob\appdata\local
Cleaning \\10.0.0.5\c$\users\amy\appdata\local
Few things to note about the code:
Get-Content "filename" | % - this is going to loop through the contents of the file one line at a time. % is a shortcut for ForEach-Object.
$_ when inside a foreach loop is an automatic variable created by PowerShell that contains the current item in the loop.
If you have a loop inside a loop and you need to access both $_ values from the inner and outer loop, you can create a new variable (eg $PC = $_;) in the outer loop that can be used within the inner loop (eg $Path = "\\$PC\c$\users\$user\appdata\local").
You should definitely learn to use functions, and then in the future you can combine functions into modules. This is a big help in organising your code, and you can avoid duplication by sharing functions between different scripts - but your current script doesnt need functions (but theyre a good idea).
Depending on your network, you might be able to use PowerShell remoting instead of the Administrative shares to achieve the same effect. This is a more advanced topic, there is some configuration required on the machines you want to connect but the advantage is your computer sends the script to each target, and the target computer runs the script and reports its results.
Another possible change i would suggest is only using a list of computers - then on each computer use get-childitem -path c:\users to actually get the list of each profile currently on that target computer.

[Powershell]: 2nd Execution of Copy-Item creates subfolder (5.1.17763.1007)

After searching for a wile I need to post my question here:
I want to do a simple task:
copy-item -path "C:\Folder Copied" -destination "C:\Folder Copied_New" -recurse
Assuming the dir "Folder Copied_New" doenst exist in "C:", PS will create a folder, and copy the folder (and its content) "C:\Folder Copied" to "Folder Copied_New"
HOWEVER, if you execute the command a second time, the following happens:
Powershell created: "C:\Folder Copied_New\Folder Copied" (content "Test.txt" was also copied to this newly created folder...
The 3rd time you execute the command, it ll say that the folder already exists...
So my question:
After I run the command a 2nd time, PS should throw an error, that "Folder Copied_New". How do I do that?
I tried copying and renaming the new folder, using and NOT using "" in the paths, but nothing worked. I do think of using -Testpath, but I thought I ask the community for a simplier (BestPractice) approache.
Thanks in advance for reading and advising!
Well explained in another question but same issue
Not an answer but to long to write in comments.
Can you run following script and show us the output?
$root = "$($env:TEMP)\test"
$source = "$($root)\Logfiles"
$destination = "$($root)\Drawings\Logs"
# Verify folders don't exist yet
if (Test-Path $source) {Throw}
if (Test-Path $destination) {Throw}
# Set up
New-Item $source, $destination -ItemType Directory -Force | Out-Null # Create folders
New-Item "$($source)\testfile" -ItemType File -Force | Out-Null # Create a testfile
# Show files/folders before copy
Write-Output "Before copy"
Get-ChildItem $root -Recurse | select fullname
# Copy first time
Copy-Item -path $source -destination $destination -Recurse -ErrorAction Continue
Write-Output "After first time copy"
Get-ChildItem $root -Recurse | select fullname
# Copy second time
Copy-Item -path $source -destination $destination -Recurse -ErrorAction Continue
Write-Output "After second time copy"
Get-ChildItem $root -Recurse | select fullname
# Copy third time
Copy-Item -path $source -destination $destination -Recurse -ErrorAction Continue
Write-Output "After third time copy"
Get-ChildItem $root -Recurse | select fullname
# Clean up
Remove-Item $source, $destination -Force -Recurse | Out-Null
On my computer, I don't observe any abnormal behavior. The source folder get's copied to destination after the first time. The second run does not alter anything, neither does the third run.
That behavior is the default:
Let's say you want to copy and rename an item, so you are doing this:
Copy-Item -path source.txt -Destination destination.txt
You expect a file named destination.txt with the content of source.txt.
If you want to copy a file but without renaming it you do this:
Copy-Item -path source.txt -Destination C:\test
You expect the file source.txt to apear in C:\test. If C:\test didn't exist it will be created.
Now let's try to copy a folder while the destination does not exist.
Copy-Item -path C:\test -Destination D:\toast
The destination didn't exist so you created it by copying the source folder to the destination. The content of the destination will be the same as the source. The folder D:\toast is the same as C:\test but it got renamed in the process.
However if you provide a destination path where the source object (folder) is going to be located, it will be located in there.
Copy-Item -path C:\test -Destination D:\toast
D:\toast did exist from our previouse action so a copy of C:\test will be created in there: D:\toast\test

Compress-Archive Error: Cannot access the file because it is being used by another process

I would like to zip a path (with a service windows running inside).
When the service is stopped, it works perfectly, when the service is running, I have the exception:
The process cannot access the file because it is being used by another
process.
However, when I zip with 7-zip, I don't have any exception.
My command:
Compress-Archive [PATH] -CompressionLevel Optimal -DestinationPath("[DEST_PATH]") -Force
Do you have any idea to perform the task without this exception?
Copy-Item allows you to access files that are being used in another process.
This is the solution I ended up using in my code:
Copy-Item -Path "C:\Temp\somefolder" -Force -PassThru |
Get-ChildItem |
Compress-Archive -DestinationPath "C:\Temp\somefolder.zip"
The idea is that you pass through all the copied items through the pipeline instead of having to copy them to a specific destination first before compressing.
I like to zip up a folder's content rather than the folder itself, therefore I'm using Get-ChildItem before compressing in the last line.
Sub-folders are already included. No need to use -recurse in the first line to do this
A good method to access files being used by another process is by creating snapshots using Volume Shadow Copy Service.
To do so, one can simply use PowerShells WMI Cmdlets:
$Path = "C:/my/used/folder"
$directoryRoot = [System.IO.Directory]::GetDirectoryRoot($Path).ToString()
$shadow = (Get-WmiObject -List Win32_ShadowCopy).Create($directoryRoot, "ClientAccessible")
$shadowCopy = Get-WmiObject Win32_ShadowCopy | ? { $_.ID -eq $shadow.ShadowID }
$snapshotPath = $shadowCopy.DeviceObject + "\" + $Path.Replace($directoryRoot, "")
Now you can use the $snapshotPath as -Path for your Compress-Archive call.
This method can also be used to create backups with symlinks.
From there on you can use the linked folders to copy backed up files, or to compress them without those Access exceptions.
I created a similiar function and a small Cmdlet in this Gist: Backup.ps1
There was a similar requirement where only few extensions needs to be added to zip.
With this approach, we can copy the all files including locked ones to a temp location > Zip the files and then delete the logs
This is bit lengthy process but made my day!
$filedate = Get-Date -Format yyyyMddhhmmss
$zipfile = 'C:\Logs\logfiles'+ $filedate +'.zip'
New-Item -Path "c:\" -Name "Logs" -ItemType "directory" -ErrorAction SilentlyContinue
Robocopy "<Log Location>" "C:\CRLogs\" *.txt *.csv *.log /s
Get-ChildItem -Path "C:\Logs\" -Recurse | Compress-Archive -DestinationPath $zipfile -Force -ErrorAction Continue
Remove-Item -Path "C:\Logs\" -Exclude *.zip -Recurse -Force

Powershell script to install a specific program. Script works but is there a better way to do everything?

Okay so the script below is originally made using Batch and i converted it to PS. It works at the moment but is there better way to do this?
First we want to check is operating system 32 or 64 bit so we'll get the right installation path. Then we want to check is there old installation folder or not and if there is, the script should stop.
If there isn't that old installation folder, we'll create one and then import the registry file.
After that, we want to change drive H: to C:\Temp and then we'll install the msi-file. When the msi-file is installed, we want to check the installation path is the "program.exe" in the right place.
If everything is ok, we want to create folder for the GCTI files and then copy all the necessary files.
At the end of the script there's couple more file copying left and then we are done.
At the moment this script is in the same folder as the necessary installation files and when we use this to install the program, we need to copy the folder to the remote computer. I am planning to change this script a bit so that at first it asks on which computer we want to install this and then it copies all the files to the specific remote computer and then runs this script in remote computer.
#Let's check is OS 32 or 64 bit
$bit = "C:\Windows\syswow64\."
$isit64bit = Test-Path $bit
If ($isit64bit -eq $True) {$installpath = "C:\Program Files (x86)"}
Else {$installpath = "C:\Program Files"}
#Let's check is there old installation folder
$Program = $installpath+"\Program\"
$Programtest = Test-Path $Program
If ($Programtest -eq $false ) {Write-Host "None found, let's continue the installation"}
Else {Write-Host "Old installation folder found, remove files and try again" Exit}
# Create ODBC-connection in registry
Start-Process -FilePath Reg -ArgumentList import, ".\Progserver_ODBC.reg" -Wait -WindowStyle Minimized
#Let's check if previous action is ok
$registry = "HKLM:\SOFTWARE\WOW6432Node\ODBC\ODBC.INI\Progserver\"
$registrycheck = Test-Path $registry
If ($registrycheck -eq $True) {Write-Host "Registrychange is ok"}
Else {Write-Host "Registrychange failed" Exit}
# Rename Drive "H:" C:\temp
New-PSDrive -Name "H" -PSProvider 'FileSystem' -Root C:\temp
# Install the msi
Start-Process -FilePath msiexec -ArgumentList /i, "Program-4.3.32.msi", /quiet -Wait
$install = "C:\Program Files (x86)\PathtoProgram.exe"
$installcheck = Test-Path $install
If ($installcheck -eq $True) {Write-Host "Installation succeeded"}
Else {Write-Host "Installation failed." Exit}
# Create GCTI's
$GCTI = "$installpath\PathToGCTI\"
If (Test-Path $GCTI) {Write-Host "GCTI folder already exists"}
Else {Write-Host "Create GCTI folder"} New-Item -ItemType Directory -Path $GCTI -Force
Copy-Item .\PathtoGCTI\* -Destination $GCTI -Recurse -Force
Write-Host "Copied GCTI-files"
# Copy program.ini ja vec.ini
Write-Host "Copying program.ini ja vec.ini"
Copy-Item .\PathToProgram.ini $installpath\PathToProgram.ini
Copy-Item .\PathToVec.ini $installpath\PathToVec.ini
# Change folder rights for the installation folder
cacls.exe $installpath\Program /T /E /G "All Users:C"
# Copy files from version 4.3.26
Copy-Item .\PathToProgram.exe $installpath\PathToProgram -Force
# Copy files
Copy-Item .\PathToFiles\* $installpath\PathToProgram\ -Force -Recurse
Set-ItemProperty $installpath\PathToProgram\graph\* -Name isreadonly $true
#Remove PSDrive
Remove-PSDrive -Name "H"
This is by no means exhaustive; just a selection of comments. You might be better submitting this to the Code Review StackExchange site.
General rules:
always used named parameters in PS functions
Your code formatting is important (carriage returns, tabbing/spacing, etc.)
e.g.
### Don't do this
Get-ChildItem "C:\temp"
### Instead do this
Get-ChildItem -Path "C:\temp"
Test if we're running a 64 bit OS and pick the appropriate Program Files folder
if ([environment]::Is64BitOperatingSystem) {
$installationPath = $env:ProgramFiles
}
else {
$installationPath = ${env:ProgramFiles(x86)}
}
When joining file paths, use Join-Path:
### So don't do this
$Program = $installpath+"\Program\"
### Instead do this
$Program = Join-Path -Path $installpath -ChildPath "Program"
When mapping your drive; be careful about scoping.
And probably best ensure you clean up after yourself, too! I have fallen foul of this in the past and it was a nightmare to clean up :-S
try {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
New-PSDrive -Name "H" -Root "C:\temp" -PSProvider FileSystem -Scope Script
### do your other stuff
catch {
throw $_.Exception
}
finally {
if (Test-Path -Path "H:\") {
Remove-PSDrive -Name "H"
}
}
Note the defensive programming (check if the drive exists already and remove it if it does!).
For bonus points you could pull the mapping code in to a separate function to avoid repetition (D.R.Y.)
Phew... I think that will do for now!
Good work :-)

Need help on Powershell Copy-Item from network drives

I am trying to use Copy-Item from remote machine to another remote machine with the command:
Copy-Item -Path "\\machine1\abc\123\log 1.zip" -Destination "\\machine2\\c$\Logs\"
I am constantly getting Error "Cannot find Path "\\machine1\abc\123\log 1.zip"
I can access that path and copy manually from there.
I am opening PowerCLI as administrator and running this script... I am absolutely stuck here and not sure how to resolve it.
This seems to work as is on PowerShell v3. I don't have v2 handy to test with, but there are two options that I'm aware of, which ought to work. First, you could map PSDrives:
New-PSDrive -Name source -PSProvider FileSystem -Root \\machine1\abc\123 | Out-Null
New-PSDrive -Name target -PSProvider FileSystem -Root \\machine2\c$\Logs | Out-Null
Copy-Item -Path source:\log_1.zip -Destination target:
Remove-PSDrive source
Remove-PSDrive target
If this is something you're going to do a lot, you could even wrap this in a function:
Function Copy-ItemUNC($SourcePath, $TargetPath, $FileName)
{
New-PSDrive -Name source -PSProvider FileSystem -Root $SourcePath | Out-Null
New-PSDrive -Name target -PSProvider FileSystem -Root $TargetPath | Out-Null
Copy-Item -Path source:\$FileName -Destination target:
Remove-PSDrive source
Remove-PSDrive target
}
Alternately, you can explicitly specify the provider with each path:
Copy-Item -Path "Microsoft.PowerShell.Core\FileSystem::\\machine1\abc\123\log 1.zip" -Destination "Microsoft.PowerShell.Core\FileSystem::\\machine2\\c$\Logs\"
this works all day for me:
$strLFpath = "\\compname\e$\folder"
$strLFpath2 = "\\Remotecomputer\networkshare\remotefolder" #this is a second option that also will work
$StrRLPath = "E:\localfolder"
Copy-Item -Path "$StrRLPath\*" -Destination "$strLFpath" -Recurse -force -Verbose
things to watch:
Copy-item define the LAST item as the object.
for copying the content of a folder you NEED the \*
If you are copying the folder it self to a new location then you do not need to declare the content.
I use this daily:
Robocopy /E \\\SOURCEIP\C$\123\ \\\DESTIP\C$\Logs\
There is an empty space in the middle. For ROBCOPY, /E does a copy. You can google if you need to do a move.
Or:
$SourceIP = Read-Host "Enter the Source IP"
$DESTIP = Read-Host "Enter the Destination IP"
Robocopy /E \\\\$SourceIP\C$\123\ \\\\$DESTIP\C$\Logs\
####Just adjust the C$ path on both#####