Extra characters when script is run as local system - powershell

I have a script with a couple of functions. The first function creates some registry keys in HKLM\Software\Test (\UDF1-30). The second function takes any strings written in the UDF# keys, contatinates them (one line per UDF, seperated by the pipe character) and copies them to HKLM\Software\CentraStage\Custom#.
For testing, I threw the following string into UDF12:
PatchSched:{"StartTime":"23:00:00","TzBias":-480,"Duration":240,"DayOfYear":[],"DayOfWeek":[-2],"Days":[],"MonthlyDayOfWeek":[6],"Months":[1,2,3,4,5,6,7,8,9,10,11,12],"Ordering":[4],"ScheduleType":7}
When I run the script as the local administrator, I get that same string into Custom12. But, when I run the script as the Local System, I get random pipes in the string:
PatchSched:{"StartTime":"23:00:00","TzBias":-480,"Duration":240,"DayOfYear":[],"DayOfWeek":[-2],"Days":[],"MonthlyD|ayOfWeek":[6],"Months":[1,2,3,4,5,6,7,8,9,10,11,12],"Ordering":[4],"ScheduleType":7}
Why in the world, would this happen? Here is the script:
Function Add-UserDefinedFields {
<#
.DESCRIPTION
This function checks if HKLM\SOFTWARE\Test exists. If not, it creates the required registry structure, to support Update-UserDefinedFields.
#>
Set-Location HKLM:
If (-Not(Test-Path .\Software\Test\UDF29)) {
# If the Test registry key does not exist...
# Create the Test registry key.
New-Item -Path .\Software -Name Test
# Create 30 UDF registry keys.
For ($i = 1; $i -le 30; $i++) {
New-Item -Path .\Software\Test -Name UDF$i
}
}
}
Function Update-UserDefinedFields {
<#
.DESCRIPTION
This function reads the value of each UDF registry entry, in HKLM\SOFTWARE\Test and writes the value(s) to the corresponding UDF in HKLM\SOFTWARE\CentraStage.
#>
Set-Location HKLM:
For ($i = 1; $i -le 30; $i++) {
# For each of the 30 UDF registry keys...
# Initialize variable.
$udfValue = New-Object "System.Collections.Generic.List[string]"
Get-ItemProperty .\SOFTWARE\Test\UDF$i -ErrorAction SilentlyContinue | Out-String -Stream | Where-Object { $_ -NOTMATCH '^ps.+' } | ForEach-Object {
$udfValue.Add($_)
}
$udfString = $udfValue -join '|'
$udfString = $udfString.Replace(' ', '')
While ($udfString -like "*||*") {
$udfString = $udfString.replace('||', '|')
}
If ($udfString) {
# Trim the leading and trailing characters (|).
$udfString = $udfString.substring(1, $udfString.length - 2)
}
Write-Host ("Writing to UDF{0}: {1}" -f $i, $udfString)
# For each Test UDF, write the concatinated value to the corresponding AEM UDF registry location.
$null = New-ItemProperty -Path .\SOFTWARE\CentraStage -Name Custom$i -PropertyType String -Value $udfstring -Force -ErrorAction SilentlyContinue
}
}
Add-UserDefinedFields
Update-UserDefinedFields

Okay, I figured it out. The code now looks like this:
Function Add-TestUserDefinedFields {
<#
.DESCRIPTION
This function checks if HKLM\SOFTWARE\Test exists. If not, it creates the required registry structure, to support Update-UserDefinedFields.
#>
Set-Location HKLM:
If (-Not(Test-Path .\Software\Test\UDF29)) {
# If the Test registry key does not exist...
# Create the Test registry key.
New-Item -Path .\Software -Name Test
# Create 30 UDF registry keys.
For ($i = 1; $i -le 30; $i++) {
New-Item -Path .\Software\Test -Name UDF$i
}
}
}
Function Update-UserDefinedFields {
<#
.DESCRIPTION
This function reads the value of each UDF registry entry, in HKLM\SOFTWARE\Test and writes the value(s) to the corresponding UDF in HKLM\SOFTWARE\CentraStage.
#>
Set-Location HKLM:
For ($i = 1; $i -le 30; $i++) {
# For each of the 30 UDF registry keys...
# Initialize variable.
$udfValue = New-Object "System.Collections.Generic.List[string]"
(Get-ItemProperty .\SOFTWARE\Test\UDF$i -ErrorAction SilentlyContinue).PSObject.Properties | Where-Object { $_.Name -NOTMATCH '^ps.+' } | ForEach-Object {
$udfValue.Add("$($_.Name):$($_.Value)")
}
$udfString = $udfValue -join '|'
Write-Output ("The value of `$udfString is {0}" -f $udfString) | out-file C:\Synoptek\test.txt -Append
$udfString = $udfString.Replace(' ', '')
While ($udfString -like "*||*") {
$udfString = $udfString.replace('||', '|')
}
Write-Output ("Writing to UDF{0}: {1}" -f $i, $udfString)
# For each Test UDF, write the concatinated value to the corresponding UDF registry location.
$null = New-ItemProperty -Path .\SOFTWARE\CentraStage -Name Custom$i -PropertyType String -Value $udfstring -Force -ErrorAction SilentlyContinue
}
}
Add-TestUserDefinedFields
Update-UserDefinedFields

Related

Get location of specific SCCM device collection in Powershell

I am writing a script to export the names of all computer in a device collection to a txt file. My script works as expected but I would like to preserve the folder structure in the exported file structure. For this I need to get the location of the Device Collection.
My Question:
Is there a way to get the location of a SCCM Device Collection in PowerShell?
I've stumbled across a few posts like this and this that use WMI and WQL for this, but I wasn't able to get those working in my script and I would like to do everything in PowerShell whenever possible.
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
$totalCollections = $collections.length
"Number of Collections: $totalCollections"
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
$i++
"Writing File $i of $totalCollections"
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
$file = New-Item -Path "C:\Temp\exporte\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
}
}
I would like to expand this code so that the txt files are placed in the corresponding subfolder analog to the SCCM file structure. E.g rootFolder/rooms/
I was using this module until now but wasn't able to find anything that gives me back the specific location of a collection.
Thanks in advance
I wasn't able to find a way to do this in plain PowerShell and the SCCM Module. In the end I did it like #FoxDeploy suggested. I made a SQL query select for each collection (performance isn't an issue in my case) on our SCCM database to get the folder path. I then used this to place the export file in the appropriate place.
This is my working example with some confidential lines removed
## Parameter ##
$exportLocation = [removed]
$sqlServer = [removed]
$db = [removed]
$query = "SELECT [ObjectPath] FROM [removed].[v_Collections] WHERE CollectionName ="
$SiteCode = [removed] # Site code
$ProviderMachineName = [removed] # SMS Provider machine name
# Customizations
$initParams = #{}
# Import the ConfigurationManager.psd1 module
if((Get-Module ConfigurationManager) -eq $null) {
Import-Module [removed]\..\ConfigurationManager.psd1" #initParams
}
# Connect to the site's drive if it is not already present
if((Get-PSDrive -Name $SiteCode -PSProvider CMSite -ErrorAction SilentlyContinue) -eq $null) {
New-PSDrive -Name $SiteCode -PSProvider CMSite -Root $ProviderMachineName #initParams
}
Set-Location "$($SiteCode):\" #initParams
# get all collections and save them to an array
$collections = (Get-CMDeviceCollection | Select -ExpandProperty "Name")
# total number of collections
$totalCollections = $collections.length
# output to console
"Number of Collections: $totalCollections"
# empty output directory
Set-Location [removed]
Remove-Item $exportLocation\* -Recurse -Force
Set-Location [removed]
# loop through all collections
$i = 0
foreach($name in $collections){
ForEach-Object -Process {
# print progress
$i++
"Writing File $i of $totalCollections"
# remove all characters, that aren't compatible with the windows file naming scheme (/\:*?"<>|)
$SanitizedName = $name -replace '/','(slash)' -replace '\\','(backslash)' -replace ':','(colon)' -replace '\*','(asterisk)' -replace '\?','(questionmark)' -replace '"','(quote)' -replace '<','(less)' -replace '>','(more)' -replace '\|','(pipe)'
# get members of collection
$collectionMembers = (Get-CMCollectionMember -CollectionName $name | Select -ExpandProperty "Name")
# write to file
Set-Location [removed]
$path = (Invoke-Sqlcmd -ServerInstance $sqlServer -Database $db -Query "$query '$collection'").Item("ObjectPath")
New-Item -ItemType Directory -Force -Path "$exportLocation$path"
$file = New-Item -Path "$exportLocation$path\$SanitizedName.txt"
Add-Content -Path $file.FullName -Value $collectionMembers
Set-Location [removed]
}
}
hope this helps someone. Thanks #FoxDeploy

How to write Get-ChildItem registry sub-key names and selected values to CSV in Powershell

I am taking a hive value and getting a list of sub-keys, then getting values of sub-key properties all defined by command line parameters. I would like to output them to CSV like so:
sub-key name | val-1 | val-2 | ... | val-n
I am not sure how to make the following script do this and could use some assistance.
param ($hive, $regkey, $type, $val)
#Set default hive to HKEY_LOCAL_MACHINE
if(!$hive){
$hive="HKLM:"
}
#Require a registry key to process
if (!$regkey){
Write-Output "Please enter a registry key!"
}
else
{
#Set up the registry key
$regkey = $hive+":"+$regkey
#Get the child items from the registry key
$registry = Get-ChildItem -Path $regkey -Recurse
#Process the values
Foreach($a in $registry) {
#List the registry key child item
$valname = $a | Split-Path -Leaf
echo "Name: " $valname
#Process each $val parameter value
foreach($value1 in $val){
($a | Get-ItemProperty).Psobject.Properties |
Where-Object {
$_.Name -like $value1
} |
Select-Object Name, Value |
Sort-Object
}
}
}

Loop in powershell

Hello Guys need some help, tips with script:
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
$missingi = "$path\$server.txt"
$plik = get-content $missingi
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
At first I create file with name of server, for example server.txt in this server we have list of KBs. Scripts searching in folder paczki that KB exist if yes copying this in server and create .bat
I would like do add automatically searching all .txt files eg server.txt, & server1.txt and use it in loop, I thought about something like that:
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
and put it in loop but its not really working, I try to add loop in this place:
for ($i in pliki_txt)
$path = ".\" # path do txt
$server="server" # server.txt
$pliki_txt= Get-ChildItem $path -Filter "*.txt" | % {$_.BaseName}
(....)
What am I doing wrong? Is there any easier way? Script is only working when I put manually set $server like $server="serwer"
You can try this:
$path = ".\"
Get-ChildItem $path -Filter *.txt | %{
$content = Get-content $_.FullName
Foreach($server in $content){
write-host $server
}
}
If I got that right, the issue here is that you're not putting the lines in the right order.
From your original code I would change the following
$path = ".\" # path do txt
$server = "server" # server.txt
$paczki = ".\paczki\"
# $missingi = "$path\$server.txt"
$missingi = Get-ChildItem -Path $path -Filter server*.txt | Select -ExpandProperty Name
foreach ($m in $missingi) {
$plik = get-content $m
( ... )
}
That way you'll check every server*.txt file in that path and process it accordingly.
Or you could even turn it into a parameterized script like this
Param(
[Parameter(Mandatory = $true)]
[String]$path,
[Parameter(Mandatory = $true)]
[String]$pattern,
[Parameter(Mandatory = $true)]
[String]$packzi
)
$missingi = Get-ChildItem -Path $path -Filter *.txt | Select -ExpandProperty | Select-String "$pattern"
foreach ($m in $missingi) {
$plik = get-content $m
foreach ($j in $plik) {
Write-Output "1"
$wynik = Get-ChildItem "$paczki" | ? {$_.name -match "$j"}
if ($wynik -eq $null) {
# Write-Host $i
}
else {
Write-Output "2"
Write-Host $wynik "znaleziono"
Copy-Item $paczki\$wynik -Destination \\$server\c$\temp\ -force
}
}
#### BAT GENERATOR #####
Write-Output "3"
# & .\bat_generator.ps1
$zapis = "$path\test.bat"
"pushd %~dp0" > $zapis
$nazwa = Get-ChildItem "\\$server\c$\temp\" | select name
foreach ($i in $nazwa) {
$text = $i.name + " /norestart /quiet"
$text >> $zapis
}
"ppd0" >> $zapis # dodaj ppd0
move-item -path .\test.bat -destination \\$server\c$\temp\ -Force # skopiuj .bat na server
}
Then you would run it like this:
.\YourScript.ps1 -path ".\" -pattern "server" -packzi ".\packzi\"
That will give you more flexibility if you want to change the source path, the name pattern or the search patch.
I hope this helps.

Powershell Find-And-Replace on registry values?

In 'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' I have some paths set to an old server. e.g.:
'My Pictures' is set to '\\DeadServer\RedirectedFolders\%UserName%\My Documents\My Pictures'
I'd like to replace "\\DeadServer\RedirectedFolders" with "C:\Users"
How can this be done in powershell?
I got as far as trying
Get-ItemProperty -path "Microsoft.PowerShell.Core\Registry::HKEY_USERS\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" | ? {$_.PSObject.Properties -like "*DeadServer*"}
But I think I'm getting confused with how the entry I want to change is a 'Property', and not an 'Item', and I don't know how to iterate through properties like I'd do Items.
Before you ask, I've already made this change with Group Policy, but it's not taking. Users are getting a message
The Recycle Bin on \DeadServer\RedirectedFolders\%UserName%\My Documents\My Pictures` is corrupted. Do you want to empty the Recycle Bin for this drive?
upon login which is keeping Folder Redirection from applying.
This is my attempt to force the change back to local storage manually.
I figured it out. Took me a long time, but I wrote a rather inelegant script:
Get-Item -ErrorAction SilentlyContinue -path "Microsoft.PowerShell.Core\Registry::HKEY_USERS\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" |
foreach {
Get-ItemProperty -Path "Microsoft.PowerShell.Core\Registry::$_" |
foreach {
$CurrentUserShellFoldersPath = $_.PSPath
$SID = $CurrentUserShellFoldersPath.Split('\')[2]
$_.PSObject.Properties |
foreach {
if ($_.Value -like "*DeadServer*") {
write-host "Path:`t`t"$CurrentUserShellFoldersPath
write-host "SID:`t`t"$SID
write-host "Name:`t`t"$_.Name
write-host "Old Value:`t"$_.Value
$newValue = $_.Value
$newValue = $newValue -replace '\\\\DeadServer\\RedirectedFolders', "C:\Users"
$newValue = $newValue -replace "My Documents\\", ""
$newValue = $newValue -replace "My ", ""
Write-Host "New Value:`t"$newValue
Set-ItemProperty -Path $CurrentUserShellFoldersPath -Name $_.Name -Value $newValue
Write-host "================================================================"
}
}
}
}
I'd love to learn of a faster or more elegant way to do this if any of you have one.
Here's an easy to use registry replace function, which can search a path recursively.
# Replace all registry key values and/or registry key names under a given path.
# Example Usage:
# RegistryValue-Replace "ExistingValue" "NewValue" 'HKEY_CURRENT_USER\Software\100000_DummyData'
# RegistryValue-Replace "ExistingValue" "NewValue" 'HKEY_USERS\*\Software\100000_DummyData' -ReplaceKeyNames $true -CaseSensitive $true
# RegistryValue-Replace 'C:\\Program Files\\Microsoft SQL Server' 'E:\Program Files\Microsoft SQL Server' 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server*' -LoggingOn $true
function RegistryValue-Replace (
[string]$OldValue = $(throw “OldValue (the current value) required.”),
[string]$NewValue = $(throw “NewValue (the replacement value) required.”),
[string]$RegkPath = $(throw “RegkPath (The full registry key path) required.”),
[bool] $CaseSensitive = $false, # If true, search and replace is case sensitive
[bool] $WholeWord = $false, # If true, searches for whole word within the value.
[bool] $ExactMatch = $false, # If true, the entire value must match OldValue, and partial replacements are NOT performed
[bool] $ReplaceKeyNames = $false, # If true, replaces registry key names
[bool] $ReplaceValues = $true,
[bool] $LoggingOn = $false )
{
$PowershellRegPrefix = 'Microsoft.PowerShell.Core\Registry::'
$MatchFor = if ($WholeWord -eq $true) {".*\b$OldValue\b.*"} else { ".*$OldValue.*" }
if ($RegkPath -NotLike "$PowershellRegPrefix*") { $RegkPath = $PowershellRegPrefix + $RegkPath }
#(Get-Item -ErrorAction SilentlyContinue -path $RegkPath) +
#(Get-ChildItem -Recurse $RegkPath -ErrorAction SilentlyContinue) |
foreach {
Get-ItemProperty -Path "$PowershellRegPrefix$_" |
foreach {
$CurrentShellFoldersPath = $_.PSPath
$SID = $CurrentShellFoldersPath.Split('\')[2]
$_.PSObject.Properties |
foreach {
if ($_.Name -cne "PSChildName" -and (($ExactMatch -eq $true -and $_.Value -clike $OldValue) -or ($ExactMatch -eq $false -and
(($CaseSensitive -eq $false -and $_.Value -match $MatchFor) -or ($CaseSensitive -eq $true -and $_.Value -cmatch $MatchFor))))) {
$Original = $_.Value
$Create_NewValue = $_.Value
$SubKeyName = $_.Name
if ($CaseSensitive -eq $true){ $Create_NewValue = $Create_NewValue -creplace $OldValue, $NewValue }
else { $Create_NewValue = $Create_NewValue -replace $OldValue, $NewValue }
if ($_.Name -eq "PSPath" -and $_.Value -eq $CurrentShellFoldersPath) {
if ($ReplaceKeyNames -eq $true) {
Move-Item -Path $CurrentShellFoldersPath -Destination $Create_NewValue
if ($LoggingOn -eq $true){ Write-host "Renamed registry key '$CurrentShellFoldersPath' to '$Create_NewValue'" }
} else {
if ($LoggingOn -eq $true){ Write-host "....Skipping renaming key '$CurrentShellFoldersPath->$SubKeyName' due to input option!!!" } }
} else {
if ($ReplaceValues -eq $true) {
Set-ItemProperty -Path $CurrentShellFoldersPath -Name $_.Name -Value $Create_NewValue
if ($LoggingOn -eq $true){ Write-host "Renamed '$Original' to '$Create_NewValue' for registry key '$CurrentShellFoldersPath->$SubKeyName'" }
} else {
if ($LoggingOn -eq $true){ Write-host "....Skipping renaming value '$CurrentShellFoldersPath->$SubKeyName' due to input option!!!" } }
}
}
}
}
}
}
Not really happy with this so I will be happy and sad if someone puts this to shame. It's been mostly tested as far as verifying that it is locating the correct keys.
If(!(Test-Path HKU:)){New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS}
$registrySearchPath = "HKU:\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
$pathToReplace = [regex]::Escape("C:\Users")
$newPath = '%USERPROFILE%'
Get-Item -path $registrySearchPath -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name |
Where-Object{$_ -match "^HKEY_USERS\\S-1-5-21"} |
ForEach-Object{
$key = $_ -replace "^HKEY_USERS","HKU:"
(Get-ItemProperty $key).psobject.Properties | Where-Object{$_.Value -match $pathToReplace} |
Select-Object Name,Value | ForEach-Object{
Set-ItemProperty -Path $key -Name $_.Name -Value ($_.Value -replace $pathToReplace,$newPath) -WhatIf
}
}
Use a Psdrive to map HKU since its not a default drive in PowerShell. Get all keys back that have at least a path to "\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders". Omit the default keys and any other localesque accounts by only looking for the ones with "S-1-5-21" as part of the key. Then for each of those that is located find every registry value with data that matchs the path you are looking for.
Set-ItemProperty -Path $key -Name $_.Name -Value ($_.Value -replace $pathToReplace,$newPath) -WhatIf
Drawing a little more attention on the last part here. With all the values that matched we replace the data with a simple -replace. I have a -WhatIf on there to be sure you test in case something bad happens. I would suggest commenting out that line and outputing just $_.Value -replace $pathToReplace,$newPath to verify that it is doing what you expect it to.
Make sure that you change the values for $pathToReplace and $newPath then Test twice, execute once.
The following is an example I use for after I rename the path of a profile for a user account:
function get-itemproperty2 {
# get-childitem skips top level key, use get-item for that
# set-alias gp2 get-itemproperty2
param([parameter(ValueFromPipeline)]$key)
process {
$key.getvaluenames() | foreach-object {
$value = $_
[pscustomobject] #{
Path = $Key -replace 'HKEY_CURRENT_USER',
'HKCU:' -replace 'HKEY_LOCAL_MACHINE','HKLM:'
Name = $Value
Value = $Key.GetValue($Value)
Type = $Key.GetValueKind($Value)
}
}
}
}
ls -r hkcu: | get-itemproperty2 | where Value -match "(.*)C:\\Users\\Admin(.*)" | ForEach-Object {
$newkey = $_.Value -replace '(.*)C:\\Users\\Admin(.*)', '$1C:\Users\dennisg$2';
if ($_.Name -eq '')
{
set-itemproperty -Path $_.Path -Name '(Default)' -Value $newkey -Type $_.Type ;
$outInfo = '****' + $_.Path + " | " + $_.Name + " | " + $newkey;
}
else
{
set-itemproperty -Path $_.Path -Name $_.Name -Value $newkey -Type $_.Type ;
$outInfo = $_.Path + " | " + $_.Name + " | " + $newkey;
}
echo $outInfo;
}
Function is from this SO question Use PowerShell to search for string in registry keys and values

Delete old files in recycle bin with powershell

Ok, I have a script I am writing in powershell that will delete old files in the recycle bin. I want it to delete all files from the recycle bin that were deleted more than 2 days ago. I have done lots of research on this and have not found a suitable answer.
This is what I have so far(found the script online, i don't know much powershell):
$Path = 'C' + ':\$Recycle.Bin'
Get-ChildItem $Path -Force -Recurse -ErrorAction SilentlyContinue |
#Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-3) } |
Remove-Item -Recurse -exclude *.ini -ErrorAction SilentlyContinue
It is working great with one exception, it checks the file parameter "LastWriteTime". That is awesome if the user deletes the file they same day they modify it. Otherwise it fails.
How can I modify this code so that it will check when the file was deleted, not when it was written.
-On a side note, if I run this script from an administrator account on Microsoft Server 2008 will it work for all users recycle bins or just mine?
Answer:
the code that worked for me is:
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e",""
$dtDeletedDate = get-date $DeletedDate
If($dtDeletedDate -lt (Get-Date).AddDays(-3))
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
}#EndIF
}#EndForeach item
It works awesome for me, however 2 questions remain...How do I do this with multiple drives? and Will this apply to all users or just me?
WMF 5 includes the new "Clear-RecycleBin" cmdlet.
PS > Clear-RecycleBin -DriveLetter C:\
These two lines will empty all the files recycle bin:
$Recycler = (New-Object -ComObject Shell.Application).NameSpace(0xa)
$Recycler.items() | foreach { rm $_.path -force -recurse }
This article has answers to all your questions
http://baldwin-ps.blogspot.be/2013/07/empty-recycle-bin-with-retention-time.html
Code for posterity:
# -----------------------------------------------------------------------
#
# Author : Baldwin D.
# Description : Empty Recycle Bin with Retention (Logoff Script)
#
# -----------------------------------------------------------------------
$Global:Collection = #()
$Shell = New-Object -ComObject Shell.Application
$Global:Recycler = $Shell.NameSpace(0xa)
$csvfile = "\\YourNetworkShare\RecycleBin.txt"
$LogFailed = "\\YourNetworkShare\RecycleBinFailed.txt"
function Get-recyclebin
{
[CmdletBinding()]
Param
(
$RetentionTime = "7",
[Switch]$DeleteItems
)
$User = $env:USERNAME
$Computer = $env:COMPUTERNAME
$DateRun = Get-Date
foreach($item in $Recycler.Items())
{
$DeletedDate = $Recycler.GetDetailsOf($item,2) -replace "\u200f|\u200e","" #Invisible Unicode Characters
$DeletedDate_datetime = get-date $DeletedDate
[Int]$DeletedDays = (New-TimeSpan -Start $DeletedDate_datetime -End $(Get-Date)).Days
If($DeletedDays -ge $RetentionTime)
{
$Size = $Recycler.GetDetailsOf($item,3)
$SizeArray = $Size -split " "
$Decimal = $SizeArray[0] -replace ",","."
If ($SizeArray[1] -contains "bytes") { $Size = [int]$Decimal /1024 }
If ($SizeArray[1] -contains "KB") { $Size = [int]$Decimal }
If ($SizeArray[1] -contains "MB") { $Size = [int]$Decimal * 1024 }
If ($SizeArray[1] -contains "GB") { $Size = [int]$Decimal *1024 *1024 }
$Object = New-Object Psobject -Property #{
Computer = $computer
User = $User
DateRun = $DateRun
Name = $item.Name
Type = $item.Type
SizeKb = $Size
Path = $item.path
"Deleted Date" = $DeletedDate_datetime
"Deleted Days" = $DeletedDays }
$Object
If ($DeleteItems)
{
Remove-Item -Path $item.Path -Confirm:$false -Force -Recurse
if ($?)
{
$Global:Collection += #($object)
}
else
{
Add-Content -Path $LogFailed -Value $error[0]
}
}#EndIf $DeleteItems
}#EndIf($DeletedDays -ge $RetentionTime)
}#EndForeach item
}#EndFunction
Get-recyclebin -RetentionTime 7 #-DeleteItems #Remove the comment if you wish to actually delete the content
if (#($collection).count -gt "0")
{
$Collection = $Collection | Select-Object "Computer","User","DateRun","Name","Type","Path","SizeKb","Deleted Days","Deleted Date"
$CsvData = $Collection | ConvertTo-Csv -NoTypeInformation
$Null, $Data = $CsvData
Add-Content -Path $csvfile -Value $Data
}
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell)
#ScriptEnd
Had to do a bit of research on this myself, the recycle bin contains two files for every file deleted on every drive in win 10 (in win 7 files are as is so this script is too much and needs to be cut down, especially for powershell 2.0, win 8 untested), an info file created at time of deletion $I (perfect for ascertaining the date of deletion) and the original file $R, i found the com object method would ignore more files than i liked but on the up side had info i was interested in about the original file deleted, so after a bit of exploring i found a simple get-content of the info files included the original file location, after cleaning it up with a bit of regex and came up with this:
# Refresh Desktop Ability
$definition = #'
[System.Runtime.InteropServices.DllImport("Shell32.dll")]
private static extern int SHChangeNotify(int eventId, int flags, IntPtr item1, IntPtr item2);
public static void Refresh() {
SHChangeNotify(0x8000000, 0x1000, IntPtr.Zero, IntPtr.Zero);
}
'#
Add-Type -MemberDefinition $definition -Namespace WinAPI -Name Explorer
# Set Safe within deleted days and get physical drive letters
$ignoreDeletedWithinDays = 2
$drives = (gwmi -Class Win32_LogicalDisk | ? {$_.drivetype -eq 3}).deviceid
# Process discovered drives
$drives | % {$drive = $_
gci -Path ($drive+'\$Recycle.Bin\*\$I*') -Recurse -Force | ? {($_.LastWriteTime -lt [datetime]::Now.AddDays(-$ignoreDeletedWithinDays)) -and ($_.name -like "`$*.*")} | % {
# Just a few calcs
$infoFile = $_
$originalFile = gi ($drive+"\`$Recycle.Bin\*\`$R$($infoFile.Name.Substring(2))") -Force
$originalLocation = [regex]::match([string](gc $infoFile.FullName -Force -Encoding Unicode),($drive+'[^<>:"/|?*]+\.[\w\-_\+]+')).Value
$deletedDate = $infoFile.LastWriteTime
$sid = $infoFile.FullName.split('\') | ? {$_ -like "S-1-5*"}
$user = try{(gpv "HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList\$($sid)" -Name ProfileImagePath).replace("$(gpv 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\ProfileList' -Name ProfilesDirectory)\",'')}catch{$Sid}
#' Various info
$originalLocation
$deletedDate
$user
$sid
$infoFile.Fullname
((gi $infoFile -force).length / 1mb).ToString('0.00MB')
$originalFile.fullname
((gi $originalFile -force).length / 1mb).ToString('0.00MB')
""
# Blow it all Away
#ri $InfoFile -Recurse -Force -Confirm:$false -WhatIf
#ri $OriginalFile -Recurse -Force -Confirm:$false- WhatIf
# remove comment before two lines above and the '-WhatIf' statement to delete files
}
}
# Refresh desktop icons
[WinAPI.Explorer]::Refresh()
This works well also as a script with the task scheduler.
Clear-RecycleBin -Force