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
Related
I am trying to match the samaccountname to the current folder patch, and it would worked fine if the folderpatch was "d:\profile\username" but instead of username its states username_S-1-5-21*....
I use the below code, but is it possible to filter everything behind the _S-1.... so it can match the username to the samaccountname ?
I use the below code, any help would be appreciated
[EDIT: added complete script]
Below is the complete script, so the issue is that i have several user folder with _S-1-5-21* behind the username in the folder of our FXLogic profile folder and need to match the samaccountname with the folder ( username - _S-1-5-21* )
I hope this explanation is more clear, and yes its a SID not a GUID always get them mixed up.
param(
[Parameter(Mandatory=$true)]
$FXLogicFolderPath,
$MoveFolderPath,
$SearchBase,
[string[]]$ExcludePath,
[switch]$FolderSize,
[switch]$MoveDisabled,
[switch]$DisplayAll,
[switch]$UseRobocopy,
[switch]$RegExExclude,
[switch]$CheckFXLogicDirectory)
Check if FXLogicFolderPath is found, exit with warning message if path is incorrect
if (!(Test-Path -LiteralPath $FXLogicFolderPath)){
Write-Warning "FXLogicFolderPath not found: $FXLogicFolderPath"
Check if MoveFolderPath is found, exit with warning message if path is incorrect
if ($MoveFolderPath) {
if (!(Test-Path -LiteralPath $MoveFolderPath)){
Write-Warning "MoveFolderPath not found: $MoveFolderPath"
exit
}}
exit
Main loop, for each folder found under FXLogic folder path AD is queried to find a matching samaccountname
$ListOfFolders = Get-ChildItem -LiteralPath "$FXLogicFolderPath" -Force | Where-Object {$_.PSIsContainer}
Exclude folders if the ExcludePath parameter is given
if ($ExcludePath) {
$ExcludePath | ForEach-Object {
$CurrentExcludePath = $_
if ($RegExExclude) {
$ListOfFolders = $ListOfFolders | Where-Object {$_.FullName -notmatch $CurrentExcludePath}
} else {
$ListOfFolders = $ListOfFolders | Where-Object {$_.FullName -ne $CurrentExcludePath}
}
}}
$ListOfFolders | ForEach-Object {
$CurrentPath = Split-Path -Path $_ -Leaf
Construct AD Searcher, add SearchRoot attribute if SearchBase parameter is specified
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$CurrentPath)"
}
if ($SearchBase) {
$ADSearcher.SearchRoot = [adsi]$SearchBase
}
Use the FullName path to look for a FXLogicdirectory attribute and replace the backslash by the \5C LDAP escape character
if ($CheckFXLogicDirectory) {
$ADSearcher.Filter = "(FXLogicdirectory=$($_.FullName -replace '\\','\5C')*)"
}
Execute AD Query and store in $ADResult
$ADResult = $ADSearcher.Findone()
If no matching samaccountname is found this code is executed and displayed
if (!($ADResult)) {
$HashProps = #{
'Error' = 'Account does not exist and has a FXLogic folder'
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
if ($MoveFolderPath) {
$HashProps.DestinationFullPath = Join-Path -Path $MoveFolderPath -ChildPath (Split-Path -Path $_.FullName -Leaf)
if ($UseRobocopy) {
robocopy $($HashProps.FullPath) $($HashProps.DestinationFullPath) /E /MOVE /R:2 /W:1 /XJD /XJF | Out-Null
} else {
Move-Item -LiteralPath $HashProps.FullPath -Destination $HashProps.DestinationFullPath -Force
}
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
If samaccountname is found but the account is disabled this information is displayed
} elseif (([boolean]((-join $ADResult.Properties.useraccountcontrol) -band 2))) {
$HashProps = #{
'Error' = 'Account is disabled and has a FXLogic folder'
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
if ($MoveFolderPath -and $MoveDisabled) {
$HashProps.DestinationFullPath = Join-Path -Path $MoveFolderPath -ChildPath (Split-Path -Path $_.FullName -Leaf)
Move-Item -LiteralPath $HashProps.FullPath -Destination $HashProps.DestinationFullPath -Force
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
Folders that do have active user accounts are displayed if -DisplayAll switch is set
} elseif ($ADResult -and $DisplayAll) {
$HashProps = #{
'Error' = $null
'FullPath' = $_.FullName
}
if ($FolderSize) {
$HashProps.SizeinBytes = [long](Get-ChildItem -LiteralPath $_.Fullname -Recurse -Force -ErrorAction SilentlyContinue |
Measure-Object -Property Length -Sum -ErrorAction SilentlyContinue | Select-Object -Exp Sum)
$HashProps.SizeinMegaBytes = "{0:n2}" -f ($HashProps.SizeinBytes/1MB)
}
Output the object
New-Object -TypeName PSCustomObject -Property $HashProps
}}
# Construct AD Searcher, add SearchRoot attribute if SearchBase parameter is specified
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$CurrentPath)"
}
if ($SearchBase) {
$ADSearcher.SearchRoot = [adsi]$SearchBase
The S-1-5-21... part is not a GUID, it's a SID (the principal's Security Identifier).
You can use the -replace operator to remove that part of the folder name:
$folderName = 'username_S-1-5-21-2855571654-3033049851-1520320983-9328'
$userName = $folderName -replace '_S-1-5.*$'
After which you can construct the desired LDAP query filter:
$ADSearcher = New-Object DirectoryServices.DirectorySearcher -Property #{
Filter = "(samaccountname=$userName)"
}
Not sure where your username is being derived from but you could do something simple with a regex replace on the $CurrentPath
$currentPath = 'username_S-1-5-21-2855571654-3033049851-1520320983-9328'
$currentPath = $currentPath -replace '_.+'
# AD Searcher here
This will replace everything after the underscore but all usernames will have to be in a consistent format or you'll have to take into account all possible formats that could be encountered.
I am trying to use the value of a switch parameter as the trigger to write to a csv file if the parameter is called with the script from the command line. However, with my current code the csv file is created whether I include the parameter or not. What's up with that?
Also, is there a better way to handle my else/if else/if else section?
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[string]$dir,
[Parameter(Mandatory=$true)]
[int]$days,
[switch]$csv=$false
)
Process {
Clear-Host
$totSize = 0
$totFiles = 0
$modDate = (Get-date).AddDays(-$days).Date
$modfiles = Get-ChildItem -Path $dir -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -ge $modDate }
If ($csv = $true){
$modfiles | Select-Object -Property FullName, Length,LastWriteTime | Export-Csv -Path .\modFiles.csv -NoTypeInformation
}
foreach ($file in $modfiles){
$totFiles = $totFiles + 1
$totSize = $totSize + $file.Length
}
If ($totSize -lt 1MB){
$outSize = $totSize/1KB
$unit = "KB"
}
elseif (($totSize -ge 1MB) -and ($totSize -lt 1GB)){
$outSize = $totSize/1MB
$unit = "MB"
}
elseif ($totSize -ge 1GB){
$outSize = $totSize/1GB
$unit = "GB"
}
$outRound = [math]::Round($outSize,2)
Write-Host $totFiles "Files"
Write-Host $outRound $unit
}
Two problems.
Do not specify a default value for a [switch] parameter. It will mess you up. Leave it off, and it will be $true if specified, and $false if
not.
When testing a logical value, such as an If statement, do not use the assignment equals (=), use the comparison equals (-eq).
If ($csv -eq $true){
$modfiles | Select-Object -Property FullName, Length,LastWriteTime | Export-Csv -Path .\modFiles.csv -NoTypeInformation
}
EDIT (Thanks #Scepticalist): Further, if the variable you are testing already holds a [bool] value, or can be implicitly converted to [bool], you don't even need the -eq $true part of the comparison, so:
If ($csv){
$modfiles | Select-Object -Property FullName, Length,LastWriteTime | Export-Csv -Path .\modFiles.csv -NoTypeInformation
}
My objective is to write a powershell script that will recursively check a file server for any directories that are "x" (insert days) old or older.
I ran into a few issues initially, and I think I got most of it worked out. One of the issues I ran into was with the path limitation of 248 characters. I found a custom function that I am implementing in my code to bypass this limitation.
The end result is I would like to output the path and LastAccessTime of the folder and export the information into an easy to read csv file.
Currently everything is working properly, but for some reason I get some paths output several times (duplicates, triples, even 4 times). I just want it output once for each directory and subdirectory.
I'd appreciate any guidance I can get. Thanks in advance.
Here's my code
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function Get-FolderItem
{
[cmdletbinding(DefaultParameterSetName='Filter')]
Param (
[parameter(Position=0,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)]
[Alias('FullName')]
[string[]]$Path = $PWD,
[parameter(ParameterSetName='Filter')]
[string[]]$Filter = '*.*',
[parameter(ParameterSetName='Exclude')]
[string[]]$ExcludeFile,
[parameter()]
[int]$MaxAge,
[parameter()]
[int]$MinAge
)
Begin
{
$params = New-Object System.Collections.Arraylist
$params.AddRange(#("/L","/S","/NJH","/BYTES","/FP","/NC","/NFL","/TS","/XJ","/R:0","/W:0"))
If ($PSBoundParameters['MaxAge'])
{
$params.Add("/MaxAge:$MaxAge") | Out-Null
}
If ($PSBoundParameters['MinAge'])
{
$params.Add("/MinAge:$MinAge") | Out-Null
}
}
Process
{
ForEach ($item in $Path)
{
Try
{
$item = (Resolve-Path -LiteralPath $item -ErrorAction Stop).ProviderPath
If (-Not (Test-Path -LiteralPath $item -Type Container -ErrorAction Stop))
{
Write-Warning ("{0} is not a directory and will be skipped" -f $item)
Return
}
If ($PSBoundParameters['ExcludeFile'])
{
$Script = "robocopy `"$item`" NULL $Filter $params /XF $($ExcludeFile -join ',')"
}
Else
{
$Script = "robocopy `"$item`" NULL $Filter $params"
}
Write-Verbose ("Scanning {0}" -f $item)
Invoke-Expression $Script | ForEach {
Try
{
If ($_.Trim() -match "^(?<Children>\d+)\s+(?<FullName>.*)")
{
$object = New-Object PSObject -Property #{
ParentFolder = $matches.fullname -replace '(.*\\).*','$1'
FullName = $matches.FullName
Name = $matches.fullname -replace '.*\\(.*)','$1'
}
$object.pstypenames.insert(0,'System.IO.RobocopyDirectoryInfo')
Write-Output $object
}
Else
{
Write-Verbose ("Not matched: {0}" -f $_)
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
Catch
{
Write-Warning ("{0}" -f $_.Exception.Message)
Return
}
}
}
}
Function ExportFolders
{
#================ Global Variables ================
#Path to folders
$Dir = "\\myFileServer\somedir\blah"
#Get all folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 800
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderPath = $SubDir.FullName
$folders = Get-ChildItem -Recurse $FolderPath -force -directory| Where-Object { ($_.LastAccessTimeUtc -le $cutOffDate)} | Select-Object FullName, LastAccessTime
ForEach ($folder in $folders)
{
$folderPath = $folder.fullname
$fixedFolderPaths = ($folderPath | Get-FolderItem).fullname
ForEach ($fixedFolderPath in $fixedFolderPaths)
{
#$fixedFolderPath
$getLastAccessTime = $(Get-Item $fixedFolderPath -force).lastaccesstime
#$getLastAccessTime
$details = #{ "Folder Path" = $fixedFolderPath; "LastAccessTime" = $getLastAccessTime}
$results += New-Object PSObject -Property $details
$results
}
}
}
}
ExportFolders
I updated my code a bit and simplified it. Here is the new code.
#Add the import and snapin in order to perform AD functions
Add-PSSnapin Quest.ActiveRoles.ADManagement -ea SilentlyContinue
Import-Module ActiveDirectory
#Clear Screen
CLS
Function ExportFolders
{
#================ Global Variables ================
#Path to user profiles in Barrington
$Dir = "\\myFileServer\somedir\blah"
#Get all user folders
$ParentDir = Get-ChildItem $Dir | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0}
#Export file to our destination
$ExportedFile = "c:\temp\dirFolders.csv"
#Duration in Days+ the file hasn't triggered "LastAccessTime"
$duration = 1
$cutOffDate = (Get-Date).AddDays(-$duration)
#Used to hold our information
$results = #()
$details = $null
#=============== Done with Variables ===============
ForEach ($SubDir in $ParentDir)
{
$FolderName = $SubDir.FullName
$FolderInfo = $(Get-Item $FolderName -force) | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$FolderLeafs = gci -Recurse $FolderName -force -directory | Where-Object {$_.PSIsContainer -eq $True} | where {$_.GetFileSystemInfos().Count -eq 0 -or $_.GetFileSystemInfos().Count -gt 0} | Select-Object FullName, LastAccessTime #| ft -HideTableHeaders
$details = #{ "LastAccessTime" = $FolderInfo.LastAccessTime; "Folder Path" = $FolderInfo.FullName}
$results += New-Object PSObject -Property $details
ForEach ($FolderLeaf in $FolderLeafs.fullname)
{
$details = #{ "LastAccessTime" = $(Get-Item $FolderLeaf -force).LastAccessTime; "Folder Path" = $FolderLeaf}
$results += New-Object PSObject -Property $details
}
$results
}
}
ExportFolders
The FolderInfo variable is sometimes printing out multiple times, but the FolderLeaf variable is printing out once from what I can see. The problem is if I move or remove the results variable from usnder the details that print out the folderInfo, then the Parent directories don't get printed out. Only all the subdirs are shown. Also some directories are empty and don't get printed out, and I want all directories printed out including empty ones.
The updated code seems to print all directories fine, but as I mentioned I am still getting some duplicate $FolderInfo variables.
I think I have to put in a condition or something to check if it has already been processed, but I'm not sure which condition I would use to do that, so that it wouldn't print out multiple times.
In your ExportFolders you Get-ChildItem -Recurse and then loop over all of the subfolders calling Get-FolderItem. Then in Get-FolderItem you provide Robocopy with the /S flag in $params.AddRange(#("/L", "/S", "/NJH", "/BYTES", "/FP", "/NC", "/NFL", "/TS", "/XJ", "/R:0", "/W:0")) The /S flag meaning copy Subdirectories, but not empty ones. So you are recursing again. Likely you just need to remove the /S flag, so that you are doing all of your recursion in ExportFolders.
In response to the edit:
Your $results is inside of the loop. So you will have a n duplicates for the first $subdir then n-1 duplicates for the second and so forth.
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
$results
}
should be
ForEach ($SubDir in $ParentDir) {
#skipped code
ForEach ($FolderLeaf in $FolderLeafs.fullname) {
#skipped code
}
}
$results
I need create this list to allow an other program to properly work. I use this code:
function analyse {
Param(
[parameter(Mandatory=$true)]
[String]$newPath
)
cd $newPath
dir | Foreach-Object {
$data = Get-Content -Path o:\******\public\ParcoursArborescence\Limitless\data.txt
if ($_.PsisContainer -eq $True) {
$testPath = $_.FullName + ";"
$name = $testPath
$testPath = $data -match [regex]::escape($testPath)
$testpath
if($testPath.Length -eq 0) {
$name | Out-File -Append "o:\******\public\ParcoursArborescence\Limitless\data.txt"
if ($_.FullName.Length -gt 248) {
"ecriture"
$result += $_.FullName + "`r"
} else {
"nouvelle analyse"
$_.Fullname
analyse $_.FullName
}
}
} else {
$testPath = $_.Directory.FullName + ";"
$name = $testPath
$testPath = $data -match [regex]::escape($testPath)
if($testPath.Length -eq 0) {
$name | Out-File -Append "o:\******\public\ParcoursArborescence\Limitless\data.txt"
$_.FullName.Length
if ($_.FullName.Length -gt 260) {
"ecriture2"
$result += $_.Directory.Name + "`r"
}
}
}
}
$result | Out-File -Append "o:\******\public\ParcoursArborescence\Limitless\bilanLimitless.txt"
}
But it takes hours and hours... I need to use this in thousands of folders. So, do you have any idea about how could it get faster ?
Maybe I'm oversimplifying things here, but why not list all the files at once, and test their FullName Length (PS 3.0 needed for the -File parameter of Get-ChildItem) ?
$maxLength = 248
Get-ChildItem $newPath -Recurse |
Where-Object { ($_.FullName.Length -gt $maxLength) } |
Select-Object -ExpandProperty DirectoryName -Unique |
Out-File "overlength_paths.txt"
For PS 2.0:
$maxLength = 248
Get-ChildItem $newPath -Recurse -File |
Where-Object { ($_.FullName.Length -gt $maxLength) -and (-not $_.PSisContainer) } |
Select-Object -ExpandProperty DirectoryName -Unique |
Out-File "overlength_paths.txt"
I'm trying to feed my function with some variables and when no variable is given it should use a default value of 30 for OlderThanDays. For one reason or another this is not working out as I'd expected.
I fixed my problem in the ForEach loop by using if($_.B -ne $null) {$OlderThanDays=$_.B} else {$OlderThanDays="30"} But I don't think this is best practice. Can anyone tell me why [Int]$OlderThanDays=30 isn't working?
Problem: When adding a line in my csv-file without defining the OlderThanDaysvariable, the default of 30 days is not used and the files are just deleted...
Thank you for your help.
CSV file:
# Correct input formats are:
#
# ServerName, LocalPath, OlderThanDays
# Ex: server, E:\SHARE\Target, 10
# Ex: server, E:\CBR\SHARE\Target
#
# UNC-Path, OlderThanDays
# Ex: \\domain\SHARE\Target, 20
# Ex: \\domain\Target
#
# If no 'OlderThanDays' is provided, a default of 30 days will be used
# Commenting out can be done with '#'
# ______________________________________________________________________
SERVER1, E:\SHARE\Target
\\domain\SHARE\Target2
Full script:
#__________________________________________________________________________________________________________________________________
$ImportFile = "S:\Input\Scheduled Task\Auto_Clean.csv"
$Password = cat "S:\Input\pwd.txt" | ConvertTo-SecureString -Force
$UserName = "domain\me"
#__________________________________________________________________________________________________________________________________
# Scriptblock for running the function in a job
$JobCall = {
# Function that removes files older than x days in all subfolders
Function Delete-OldFiles {
[CmdletBinding(SupportsShouldProcess=$True)]
Param(
[Parameter(Mandatory=$True,Position=1)]
[ValidateScript({Test-Path $_})]
[String]$Target,
[Parameter(Mandatory=$False,Position=2)]
[Int]$OlderThanDays=30,
[Parameter(Mandatory=$False,Position=3)]
[String]$Server,
[switch]$CleanFolders
)
#__________________________________________________________________________________________________________________________________
# Create logfile
$TempDate = (get-date).ToString("dd-MM-yyyy")
$TempFolderPath = $Target -replace '\\','_'
$TempFolderPath = $TempFolderPath -replace ':',''
$TempFolderPath = $TempFolderPath -replace ' ',''
$script:LogFile = "\\DEUSTHEIDIT02\Log\Scheduled Task\Auto_Clean\$Server - $TempFolderPath - $TempDate.log"
#__________________________________________________________________________________________________________________________________
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove files older than (FASTER)
Get-ChildItem -Path $Target -Recurse -File |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append -Verbose}
Else {
# PowerShell 2 Remove files older than
Get-ChildItem -Path $Target -Recurse |
Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append }
#__________________________________________________________________________________________________________________________________
# Switch -CleanFolders deletes empty folders older than x days
if ($CleanFolders) {
# Check the version of PowerShell
if ($PSVersionTable.PSVersion.Major -ge "3") {
# PowerShell 3+ Remove empty folders older than (FASTER)
Get-ChildItem -Path $Target -Recurse -Force -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
Where-Object { (Get-ChildItem -Path $_.FullName -Recurse -Force -File) -eq $null } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
"$Timestamp | REMOVED: $Server $Item"
}
} | Tee-Object $LogFile -Append
}
else {
# PowerShell 2 Remove empty folders older than
Get-ChildItem -Path $Target -Recurse -Force -ErrorAction SilentlyContinue |
Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } |
Where-Object { $_.CreationTime -lt (Get-Date).AddDays(-$OlderThanDays) } |
ForEach {
$Item = $_.FullName
Remove-Item $Item -Recurse -Force -ErrorAction SilentlyContinue
# Log succes/failure
$Timestamp = (Get-Date).ToShortDateString()+" | "+(Get-Date).ToLongTimeString()
if (Test-Path $Item) {
Write-Host "$Timestamp | FAILLED: $Server $Item (IN USE)"
"$Timestamp | FAILLED: $Server $Item (IN USE)"
}
else {
Write-Host "$Timestamp | REMOVED: $Server $Item"
"$Timestamp | REMOVED: $Server $Item"
}
} | Out-File $LogFile -Append
}
}
}
# Lact command of the ScriptBlock: Call the magic to happen
Delete-OldFiles $args[0] $args[1] $args[2] -CleanFolders:$args[3]
}
#__________________________________________________________________________________________________________________________________
# Read input file and ignore all lines starting with #
$File = (Import-Csv -Path $ImportFile -Header "A", "B", "C", "D" | Where { $_.A -NotLike "#*" } )
#__________________________________________________________________________________________________________________________________
# If the UNC Path is provided we will run the script locally else it wil be run on the remote server as a job
Foreach ($_ in $File) {
# Define input format & default values
if ($_.A -like "\\*") {
$Server="UNC"
$Target=$_.A
$OlderThanDays=$_.B
$CleanFolders=$_.C
}
else {
$Server=$_.A
$Target=$_.B
$OlderThanDays=$_.C
$CleanFolders=$_.D
}
# Call the scriptblock with the function to run locally or on the remote server
if ($Server -eq "UNC")
{
Write-Host "UNC Path detected: $Target, $OlderThanDays" -ForegroundColor Yellow
Start-Job -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -Name DelFiles
}
else
{
Write-Host "Local path detected: $Server, $Target, $OlderThanDays" -ForegroundColor Cyan
$Credentials = New-Object System.Management.Automation.PSCredential -ArgumentList $UserName,$Password
Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
# Delete empty folders: Invoke-Command -ScriptBlock $JobCall -ArgumentList ($Target, $OlderThanDays, $Server, $true) -ComputerName "$Server.domain" -Authentication Credssp -Credential $Credentials -AsJob -JobName DelFiles
}
}
The parameter can't be both Mandatory and have default value. Former is checked first, and if it's set to $true, than default value is simply ignored. If you want to make sure that users can't specify empty value, just use [ValidateNotNullorEmpty()] validation, and make parameter optional.
So I had a similar problem. After months keeping a module with personal functions for day-to-day activities making extensive use of default values and, in many cases, mandatory parameters with initialized default values, suddenly my functions stopped working.
After writing a new one, calling it with explicit parameters would result in the parameters values being empty in the function execution context.
After restarting powershell console for some times I decided to reboot the machine, and everything went back to normal. Go figure.