Hello, friends!
I was impressed to know that powershell scripts can help me solve some problems for my 3d max plugins.
Here is an example: There are cases when you download a 3d model with textures that have unicode names.
If your system Code Page is different from the character set of texture name, then 3d max will see such names like ??? ???.jpg.
Here is where powershell can help.
In 3d max if a texture file has a “?” in the name or in the path, it will say - the file does not exist.
The only solution I found is to save from 3ds max the path where powershell will search and the name ??? ???.jpg, then I run powershell code and it compares all the filenames from the directory with the name ??? ???.jpg and renames the texture wich maches this name.
At the moment everything works fine, and I get the renamed texture back and I relink it to my max file.
But there are still some situations that I met during using the plugin:
#1 when there are 2 ore more files which can match the name ??? ???.jpg (I don’t know at the moment how to solve this problem)
#2 when absolutely the same file is saved in different subfolders in the specified path with the same unicode name.
For now I want you to help me with #2.
In the powershell script that I use, I collect all found textures that match my texture name in 2 arrays:
`
if ($item.Name -like $Pattern)
{
$ArrTextureList.Add($item)
$ArrFullPathTextureList.Add($item.FullName)
`
}
If $ArrTextureList.Count != 1 then compare all array items by realName, FileHash and size and print “true” if all items are identical or false in other cases.
I stopped here and I don’t know how to continue.
`foreach ($texture in $ArrFullPathTextureList)
{
Write-Host ($texture)
Write-Host (Get-FileHash $texture).hash
}`
Can you give an example of the types of names you are working with? Specifically where you run into the conflict, or are you saying the name of the file literally has 6 question marks followed by a space and three question marks followed by .jpg?
Probably just showing the results of get-childitem for one of the directories as an example would be the most helpful.
of course.
In 3ds max I have: ??? ???.jpg
In the path in different subfolders I have 2 identical files with this name: Копия балка.jpg
but they have modification date different, however completely the same.
I’m using get-childitem
$items = Get-ChildItem -Force -Recurse -Path $Path | sort @{expression = {$_.FullName.Split('').Count}} -descending -ErrorAction SilentlyContinue
foreach ($item in $items)
{
if ($item.Name -like $Pattern)
{
$ArrTextureList.Add($item)
$ArrFullPathTextureList.Add($item.FullName)
}
}
foreach ($texture in $ArrFullPathTextureList)
{
Write-Host ($texture)
Write-Host ($texture.name)
Write-Host (Get-FileHash $texture).hash
}
So something like this?
Parent Folder
|
--SubFolder1
|
--Копия балка.jpg
|
--SubFolder2
|
--Копия балка.jpg
And the goal is to rename the file under subfolder1, but not under subfolder2?
no, no!
with the renaming everything is done already.
I need this:
Compare all array items of $ArrFullPathTextureList by realName, FileHash and size and print “true” if all items are identical or false in other cases.
if item1.name == item2.name and Filehash(item1)== FileHash(item2) and Filesize(item1)==Filesize(item2) then Write-Host(“true”) elseWrite-Host(“false”)
I would make it easily in maxscript, but I don’t know how to do it in powershell
Ok, first we need to change the second foreach loop to use $ArrTextureList instead of $ArrFullPathTextureList since the full path array only contains the fullname property, not all of the other properties, such as length and name. Then we can reason that we just need to compare each returned file to the first file in the array as the baseline. If the current file is different than the first file then it is different and false should be returned.
$Pattern = "????? ?????.jpg"
$ArrTextureList = @()
$ArrFullPathTextureList = @()
$items = Get-ChildItem -Force -Recurse -Path $Path | sort @{expression = {$_.FullName.Split('\').Count}} -descending -ErrorAction SilentlyContinue
foreach ($item in $items)
{
if ($item.Name -like $Pattern)
{
$ArrTextureList += $item
$ArrFullPathTextureList += $item.FullName
}
}
foreach ($texture in $ArrTextureList)
{
Write-Host ($texture.FullName)
Write-Host ($texture.name)
Write-Host (Get-FileHash $texture.FullName).hash
if ($ArrTextureList[0].name -eq $texture.name -and (Get-FileHash ($ArrTextureList[0].FullName)).hash -eq (Get-FileHash ($texture.FullName)).hash -and $ArrTextureList[0].Length -eq $texture.Length)
{
Write-Host "true"
}
else
{
Write-Host "false"
}
}
Remove-Variable ArrTextureList
Remove-Variable ArrFullPathTextureList
Note: edited to facilitate for subfolders. Using FullName for Get-FileHash instead of just the name.
You can also shorten it a bit by moving your filter into the get-childitem cmdlet and only looping through your results once.
$Pattern = "????? ?????.jpg"
$items = Get-ChildItem -Filter $Pattern -Force -Recurse -Path $Path | sort @{expression = {$_.FullName.Split('\').Count}} -descending -ErrorAction SilentlyContinue
foreach ($item in $items)
{
Write-Host ($item.FullName)
Write-Host ($item.name)
Write-Host (Get-FileHash $item.FullName).hash
if ($items[0].name -eq $item.name -and (Get-FileHash ($items[0].FullName)).hash -eq (Get-FileHash ($item.FullName)).hash -and $items[0].Length -eq $item.Length)
{
Write-Host "true"
}
else
{
Write-Host "false"
}
}
Results Similar to:
F:\temp\Powershell\part\New folder\Копия балка.jpg
Копия балка.jpg
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
true
F:\temp\Powershell\part\New folder (2)\Копия балка.jpg
Копия балка.jpg
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
true
F:\temp\Powershell\part\New folder (3)\Копия балка.jpg
Копия балка.jpg
F0E4C2F76C58916EC258F246851BEA091D14D4247A2FC3E18694461B1816E13B
false
oh, so nice, Curtis Smith!
thank you so much. In fact I was thinking to compare every 2 items
for s=1 to array.count-1 do
for f=s+1 to array.count do
…
but you’re right, it’s enough to compare just with the first file. Now it’s much clear for me. thank you very very much. I will test the code tomorrow and let you know, because I am not at the computer right now.
by the way, how to get the file with the shortest path if the files are equal?
The simple answer is use the length of the FullName as your comparison value, but let’s talk again about the scenario. In this scenario, there is no shortest path. Both paths are 40 characters.
Parent Folder
|
--SubFolder1
|
--Копия балка.jpg
|
--SubFolder2
|
--Копия балка.jpg
"Parent Folder\SubFolder1\Копия балка.jpg".Length
"Parent Folder\SubFolder2\Копия балка.jpg".Length
40
40
let’s say we have 3 identical files, 1 of them is located much deeper in subfolders, but 2 of them have the same folder depth with the same lenght, then we should print the first from these 2
Well, you are already sorting your results by directory depth, but you are sorting them descending, So the longest depth is at the top, you want the shortest depth, so remove the -descending parameter and it will default to ascending. Then you just pick the first element ([0]) of the resulting array of file objects.
$Pattern = "????? ?????.jpg"
$items = Get-ChildItem -Filter $Pattern -Force -Recurse -Path $Path | sort @{expression = {$_.FullName.Split('\').Count}} -ErrorAction SilentlyContinue
Write-Host ($items[0].FullName)
Write-Host ($items[0].name)
Write-Host (Get-FileHash $items[0].FullName).hash
Results in:
F:\Temp\Powershell\part\New folder\Копия балка.jpg
Копия балка.jpg
E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855
Hey, Curtis, thank you so much for your help.