How to limit recursive verbose output? - powershell

I have the following script that removes files and any folders matching the name. recurse is needed to avoid confirmation prompt.
Invoke-Command -Computer $Server -ScriptBlock {
param ($dir, $name)
$f = Get-ChildItem -Path $dir | Where {$_.Name -Match "$name"}
If ($f) {
$f | Foreach {
Remove-Item $_.fullname -confirm:$false -Recurse -Verbose
}
}
else {
Write-Verbose "No file found"
}
} -ArgumentList $Directory, $DB
i get a TON of verbose messages for every single one of those items saying
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem1
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem2
VERBOSE: Performing the operation "Remove Directory" on target
\name1.db
can i make it so that it just prints verbose on a folder level instead for every single subitem? essentially i would like only an output like this:
VERBOSE: Performing the operation "Remove Directory" on target \name1
VERBOSE: Performing the operation "Remove Directory" on target
\name1.db

Adding -Verbose to Remove-Item will always cause it to list out every item that it is removing (i.e. that's the point of Verbose output. It's a fire hose that's either on or off).
If you want, not necessarily less logging, but filtered logging, then the only real option is to remove -Verbose and do it yourself.
For ex.:
Invoke-Command -Computer $Server -ScriptBlock {
param ($dir, $name)
$f = Get-ChildItem -Path $dir | Where {$_.Name -Match "$name"}
If ($f) {
$f | Foreach {
#Check to see if it is a folder
if($_.PSIsContainer)
{
Write-Host "Removing Directory: $($_.FullName)"
}
Remove-Item $_.fullname -confirm:$false -Recurse
}
}
else {
Write-Verbose "No file found"
}
} -ArgumentList $Directory, $DB

Related

Powershell exclude pdf extension files

I am trying to run powershell script to separate files of all extensions by iterating all the subfolders and creating a subfolder attachments at depth 3 except pdf but it is not working. Can someone help me out by pointing what I am doing incorrectly in script.
Thanks in advance
ForEach($Folder in (Get-ChildItem -Directory .\*\*\*)){
echo "Done"
Get-ChildItem -path $Folder -Exclude *.pdf | Move-Item -Destination $Folder\Attachments -ErrorAction Stop
}
I'd suggest adding the -WhatIf parameter to Move-Item to troubleshoot what that command is actually trying to do. It will probably be quite clear from the output that it's not doing what you think it's doing.
My guess is the problem is the fact that $Folder contains a DirectoryInfo item. The default string expansion for that is just the name, and you probably want the FullName.
Try:
ForEach($Folder in (Get-ChildItem -Directory .\*\*\*)){
echo "Done"
Get-ChildItem -path $Folder -Exclude *.pdf | Move-Item -Destination "$($Folder.FullName)\Attachments" -ErrorAction Stop
}
However, it's not clear from your question what you're actually trying to accomplish, primarily because "it doesn't work" is not a problem description. I'm not sure where the attachments folder is supposed to be.
Extending from my comment. Try something like this.
'PowerShell -Whatif or Confirm'
$Destination = 'D:\temp'
(Get-ChildItem -Directory -Recurse -Exclude '*.pdf' -Depth 3) |
ForEach-Object {
Write-Verbose -Message "Processing: $PSItem" -Verbose
# Remove the whatIf after you validate the results of the move and run it again.
Try {Move-Item -Path $PSItem.FullName -Destination "$Destination\Attachments" -ErrorAction Stop -WhatIf}
Catch
{
Write-Warning -Message "Error processing request"
$PSItem.Exception.Message
}
}
# Results
<#
VERBOSE: Processing: D:\Scripts\.vs
What if: Performing the operation "Move Directory" on target "Item: D:\Scripts\.vs Destination: D:\temp\Attachments".
VERBOSE: Processing: D:\Scripts\.vs\Scripts
What if: Performing the operation "Move Directory" on target "Item: D:\Scripts\.vs\Scripts Destination: D:\temp\Attachments".
VERBOSE: Processing: D:\Scripts\.vs\Scripts\v16
What if: Performing the operation "Move Directory" on target "Item: D:\Scripts\.vs\Scripts\v16 Destination: D:\temp\Attachments".
...
#>
If you want to see what is happening under the covers as part of your check, you can do this.
Trace-Command
# Review what the command expression is doing
Trace-Command -Name metadata,parameterbinding,cmdlet -Expression {
$Destination = 'D:\temp'
(Get-ChildItem -Directory -Recurse -Exclude '*.pdf' -Depth 3) |
ForEach-Object {
Write-Verbose -Message "Processing: $PSItem" -Verbose
# Remove the whatIf after you validate the results of the move and run it again.
Try {Move-Item -Path $PSItem.FullName -Destination "$Destination\Attachments" -ErrorAction Stop -WhatIf}
Catch
{
Write-Warning -Message "Error processing request"
$PSItem.Exception.Message
}
}
} -PSHost

Stop process thats using a file remotely

I have a script in which Some of the files get deleted...
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem1
VERBOSE: Performing the operation "Remove Directory" on target
\name1\subitem2
but others throw this error following the other verbose messages:
Cannot remove item d:\temp\name1.db\metadata.sqlitedb: The process
cannot access the file 'metadata.sqlitedb' because it is being used by
another process.
Directory d:\temp\name1.db cannot be removed because it is not empty.
How can i automatically kill the process used (whichever it is) and attempt to remove the item again as part of my script above?
i tried handle suggestion from this thread PowerShell script to check an application that's locking a file?, but i think our servers dont allow external tools as i either dont get any output or getting access denied...so im looking for some other option that doesnt require external tool
Essentially looking for something like this as part of my script:
$Directory = "d:\temp"
Invoke-Command -Computer $Server -ScriptBlock {
param ($dir, $name)
#Write-Output "dir='$dir', name='$name'"
$f = Get-ChildItem -Path $dir | Where {$_.Name -Match $name} | Select -ExpandProperty FullName
if ($f) {
$f | Foreach {
try
{
Remove-Item $_ -confirm:$false -recurse -Verbose #-WhatIf
}
catch
{
if ($_.Exception.Message -like '*it is being used by another process*')
{ write-host "that process is " $pid + $pname
try{
KILL PROCESS
Remove-Item $_ -confirm:$false -recurse -Verbose #-WhatIf
}
catch
{
$error[0]
}
}
else
{
Write-Host "$($error[0])`r`n" -foregroundcolor magenta -backgroundcolor black
}
}
}
}
else {
Write-Verbose "No file found"
}
} -ArgumentList $Directory, $DB -verbose

Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Path'. Specified error

I am getting the below error when running powershell script
Set-Location : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'Path'. Specified
error 15-May-2018 08:31:42 method is not supported.
Below is the script which is having issues, Could you please suggest what is wrong here
cd $lsolutionPath
Get-ChildItem -Path "$lsolutionPath" -Filter "*Tests" -Recurse -Directory | where {$_.FullName -inotlike "*.sonarqube*"} | ForEach-Object {
$fullName = $_.FullName
$projName = $_.BaseName
write-output $projName
write-output $fullName
Write-Output "Starting Build Helper unit test run:3"
$tests = Get-ChildItem -Path "$fullName" -Recurse -Include *.dll
Write-Output "Starting Build Helper unit test run:4"
if($tests -eq $null) {
Write-Error "Could not find *Tests.dll"
return 999
}
cd $tests.Directory
Write-Output $tests.Directory
Write-Output "target args"
$targetArgs = "\""$tests\"" -nologo -parallel none -noshadow -xml \""$xUnittestResultsPath\$projName.xml\"" -nunit \""$testResultsPath\$projName.xml\"""
Write-Output "$target args"
Write-Output "###### Target Args:"**
$tests = Get-ChildItem -Path "$fullName" -Recurse -Include *.dll
cd $tests.Directory
cd is an alias for Set-Location and $tests contains multiple dll files, and so $tests.directory is an array of multiple files, that is what shows up as System.Object[]. You can't change into all of them at the same time.
It is not clear to me which one you want to change into, since you have -recurse, so there may be many different directories. Perhaps you need get-childitem ... | Select-Object -First 1 or perhaps you need a loop over them to process each one.
The $tests variable is an array of file objects, and so you may simply need to create another foreach loop for the $tests array.
Replace this:
cd $tests.Directory
Write-Output $tests.Directory
Write-Output "target args"
$targetArgs = "\""$tests\"" -nologo -parallel none -noshadow -xml \""$xUnittestResultsPath\$projName.xml\"" -nunit \""$testResultsPath\$projName.xml\"""
Write-Output "$target args"
Write-Output "###### Target Args:"**
With this:
$tests | foreach {
Set-Location $_.Directory
Write-Output $_.Directory
Write-Output "target args"
$targetArgs = "\""$($_.FullName)\"" -nologo -parallel none -noshadow -xml \""$xUnittestResultsPath\$projName.xml\"" -nunit \""$testResultsPath\$projName.xml\"""
Write-Output "$target args"
Write-Output "###### Target Args:"**
}

Powershell copy and rename files

I'm trying to copy files from a source folder to a destination folder, and rename the files in the process.
$Source = "C:\Source"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
$Destination = "\\Server01\Destination"
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -
Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
The problem is that since Get-ChildItem doesn't throw an error message if the file is not found, but rather just gives you a blank, I end up with a folder called File01.test in destination if no file named File* exists in $Source.
If it does exist, the copy operation carries out just fine. But I don't want a folder to be created if no matching files exist in $Source, rather I just want an error message logged in a log file, and no file operation to occur.
This shouldn't matter what the file name is, but it won't account for files that already exist in the destination. So if there is already File01.txt and you're trying to copy File01.txt again you'll have problems.
param
(
$Source = "C:\Source",
$Destination = "\\Server01\Destination",
$Filter = "File*"
)
$Files = `
Get-ChildItem -Path $Source `
| Where-Object -Property Name -Like -Value $Filter
for ($i=0;$i -lt $Files.Count;$i++ )
{
$NewName = '{0}{1:D2}{3}' -f $Files[$i].BaseName,$i,$Files[$i].Extension
$NewPath = Join-Path -Path $Destination -ChildPath $NewName
try
{
Write-Host "Moving file from '$($Files[$i].FullName)' to '$NewPath'"
Copy-Item -Path $Files[$i] -Destination
}
catch
{
throw "Error moving file from '$($Files[$i].FullName)' to '$NewPath'"
}
}
You can add an "if" statement to ensure that the code to copy the files only runs when the file exists.
$Source = "C:\Source"
$Destination = "\\Server01\Destination"
$File01 = Get-ChildItem $Source | Where-Object {$_.name -like "File*"}
if ($File01) {
Copy-Item "$Source\$File01" "$Destination\File01.test" -Force -Confirm:$False -ErrorAction silentlyContinue
if(-not $?) {write-warning "Copy Failed"}
else {write-host "Successfully moved $Source\$File01 to
$Destination\File01.test"}
} else {
Write-Output "File did not exist in $source" | Out-File log.log
}
In the "if" block, it will check to see if $File01 has anything in it, and if so, then it'll run the subsequent code. In the "else" block, if the previous code did not run, it'll send the output to the log file "log.log".

When using Get-ADUser -Filter {}, process is slow

I am trying to clear out some orphaned user shares on a DFS share. I want to compare the full name of the folder to the HomeDirectory property of a specified object using Get-ADUser -Filter.
If I use for instance (Get-ADUser $varibale -Properties * | Select Homedirectory) I get an error displayed when the account is not found. So I used -Filter to hide the error if there is no account found. However, this is much slower than the -Properties * | Select method.
Script:
$path = Read-Host -Prompt "Share path...."
$Dirs = Get-ChildItem $Path
foreach ($D in $Dirs) {
$Login = Get-ADUser -Filter {HomeDirectory -eq $d.FullName}
if ($d.FullName -ne $Login."HomeDirectory") {
$host.UI.RawUI.WindowTitle = "Checking $d..."
$choice = ""
Write-Host "Comparing $($d.FullName)......." -ForegroundColor Yellow
$prompt = Write-Host "An account with matching Home Directory to $($d.FullName) could not be found. Purge $($d.fullname)?" -ForegroundColor Red
$choice = Read-Host -Prompt $prompt
if ($choice -eq "y") {
function Remove-PathToLongDirectory {
Param([string]$directory)
# create a temporary (empty) directory
$parent = [System.IO.Path]::GetTempPath()
[string] $name = [System.Guid]::NewGuid()
$tempDirectory = New-Item -ItemType Directory -Path (Join-Path $parent $name)
robocopy /MIR $tempDirectory.FullName $directory
Remove-Item $directory -Force
Remove-Item $tempDirectory -Force
}
# Start of Script to delete folders from User Input and Confirms
# the specified folder deletion
Remove-PathToLongDirectory $d.FullName
}
} else {
Write-Host "Done!" -ForegroundColor Cyan
}
Write-Host "Done!" -ForegroundColor Cyan
}
You have a couple of suboptimal things in your code (like (re-)defining a function inside a loop, or creating and deleting an empty directory over and over), but your biggest bottleneck is probably that you do an AD query for each directory. You should be able to speed up your code considerably by making a single AD query and storing its result in an array:
$qry = '(&(objectclass=user)(objectcategory=user)(homeDirectory=*)'
$homes = Get-ADUser -LDAPFilter $qry -Properties HomeDirectory |
Select-Object -Expand HomeDirectory
so that you can check if there is an account with a given home directory like this:
foreach ($d in $dirs) {
if ($homes -notcontains $d.FullName) {
# ... prompt for deletion ...
} else {
# ...
}
}
Performance-wise I didn't notice a difference between the robocopy /mir approach and Remove-Item when deleting a 2 GB test folder with each command. However, if you have paths whose length exceeds 260 characters you should probably stick with robocopy, because Remove-Item can't handle those. I would recommend adding a comment explaining what you're using the command for, though, because the next person reading your script is probably as confused about it as I was.
$empty = Join-Path ([IO.Path]::GetTempPath()) ([Guid]::NewGuid())
New-Item -Type Directory -Path $empty | Out-Null
foreach ($d in $dirs) {
if ($homes -notcontains $d.FullName) {
# ... prompt for confirmation ...
if ($choice -eq 'y') {
# "mirror" an empty directory into the orphaned home directory to
# delete its content. This is used b/c regular PowerShell cmdlets
# can't handle paths longer than 260 characters.
robocopy $empty $d.FullName /mir
Remove-Item $d -Recurse -Force
}
} else {
# ...
}
}
Remove-Item $empty -Force
There's also a PowerShell module built on top of the AlphaFS library that supposedly can handle long paths. I haven't used it myself, but it might be worth a try.