Author Posts

November 30, 2015 at 2:50 am

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
}`

November 30, 2015 at 12:04 pm

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.

November 30, 2015 at 12:07 pm

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.

November 30, 2015 at 12:09 pm

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
}

November 30, 2015 at 12:12 pm

So something like this?

Parent Folder
             |
             --SubFolder1
                         |
                         --Копия балка.jpg
             |
             --SubFolder2
                         |
                         --Копия балка.jpg

November 30, 2015 at 12:14 pm

And the goal is to rename the file under subfolder1, but not under subfolder2?

November 30, 2015 at 12:16 pm

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.

November 30, 2015 at 12:18 pm

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

November 30, 2015 at 12:40 pm

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.

November 30, 2015 at 2:12 pm

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

November 30, 2015 at 2:23 pm

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?

November 30, 2015 at 2:54 pm

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

November 30, 2015 at 3:01 pm

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

November 30, 2015 at 9:36 pm

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

May 24, 2016 at 2:21 pm

Hey, Curtis, thank you so much for your help.