Cleaning up filenames from MAC using Powershell - powershell

I'm writing a script that iterates over files that are copied from a MAC computer to a Exfat disk and checks the name of the files for Windows forbidden characters.(Writing it in PowerShell)
And hopefully replace the forbidden characters with another character, for example a "-".
Why i am doing this is because i see it as a good way for me to practice coding and it might be used in my work when we get users with a lot of local files that we want to move to Onedrive.(Onedrive has a function to rename but it doesn't touch the forbidden characters, and i don't know Bash)
The issue is when I'm trying to find the characters within the script itself, it cant find the characters if i write them in the script.(For example if i write that it should look for ">")
Even if i escape the characters it just skips it(Or rather doesn't find it).
It just skips over the file i know has one in it, at first i though it might be due to encoding, but no matter what i use as a default encoding it wont display the filename correctly.(I assume this is due to how Windows reads filenames?)
edit: These are the forbidden characters im goint to look for " * : < > ? / \ |
The script itself is able to remove letters and stuff if i ask it to.
I also tried getting the char of the byte([byte][char]"") but i get this error:
Cannot convert value "" to type "System.Byte". Error: "Value was either too large or too small for an unsigned byte." Edit: the  changed by itself during the day not sure what to say.
If i just add it to the function it just returns the error:Rename-Item : The input to the script block for parameter 'NewName' failed. Exception calling "Replace" with "2" argument(s): "String cannot be of zero length.
The characters are displayed like this in visual studio code.
Any ideas are welcome or if you know of any better ways of doing it?(Maybe its better if i just learn bash. )
Here is the script itself:
edit: cleaned up the script abit and some small changes.
$provided_path = Read-Host "What Directory and its subfolders do you want to check?"
# Iterates over the folders and files within.
Write-Host "Creating list of files..."
$dictionary_filenames = ""
$dictionary_filenames = Get-ChildItem -Path $provided_path -Recurse -Force -File | Select-Object FullName,BaseName,Extension
# Resetting counter
$counter_skipped = 0
# Function for character replacment
function rename_file_name($names_function,$forbidden_char){
$old_name = $names_function.BaseName
$new_name = $names_function | Rename-Item -LiteralPath $names_function.FullName -NewName{$_.BaseName.Replace("$forbidden_char","-") + $_.extension} -PassThru
if($old_name -ne $new_name.BaseName){
Write-host "$old_name changed to $new_name"
}
}
foreach($names in $dictionary_filenames){
if($names.BaseName[0] -eq "." -and $names.BaseName[1] -match "_"){
$counter_skipped ++
$counter ++
continue
}
else{
if($names.BaseName[0] -eq " " -or $names.BaseName[-1] -eq " "){
$old_name = $names.BaseName
$new_name = $names | Rename-Item -LiteralPath $names.FullName -NewName{$_.BaseName.trim() + $_.extension} -PassThru
Write-Host "Trimming whitespace: $old_name"
}
else{
Write-Host "Trimming not needed "$names.BaseName
}
Write-host "Checking forbidden characters "$names.BaseName
rename_file_name $names ">"
rename_file_name $names "<"
rename_file_name $names "/"
}
}
Write-Host "Files checked: "$dictionary_filenames.Count
Write-Host "Files Skipped: "$counter_skipped
Read-Host 'Close window by pressing "Enter"'```

 is U+F021, two bytes long or [int16]. You can put it in a utf8 with bom encoded script.

Related

Counting in powershell?

Can someone explain to me whats going on here? Its a piece of code i got from a script we use here at work and i believe that i understand why it counts but, from there im lost. Any generalization on why/how it does so would be greatly appreciated.
Please note, i did search everywhere before asking on here.
$gc = Get-ChildItem C:\users | Select-Object -ExpandProperty Name
$ls = #($gc)
$gcls = $ls.count
For($i=0; $I -lt $gcls; $i++){
Write-host "$($i): $($ls[$i])"
}
$selection = Read-Host "Enter Number"
$selection = $selection -split " "
$gc[$selection]
gc is self explanatory.
ls is as well throwing the output into an array
gcls is creating the variable to the list of counted strings
I kinda understand whats going on in the for statement where its setting $i to 0, saying if $i -lt the counted strings in $gcls (which it is due to $i=0), and it is counting the output. Now im still kind of following but, I just don't seem to understand how its outputting the strings the way it is.
Anyone familiar with this?
Lee_Dailey also answered this above as a comment.
Inlined comments explaining what each line does and where the count comes from, how the write-host works, etc.
$gc = Get-ChildItem C:\users | Select-Object -ExpandProperty Name #gets all items in c:\users
$ls = #($gc) #this seems redundant to me, but, puts output from get-childitem above into $ls
$gcls = $ls.count #stores a count of items found in get-childitem in $gcls
For($i=0; $I -lt $gcls; $i++){
<#
check out https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_for?view=powershell-7.1
specifically:
The For statement (also known as a For loop) is a language construct you can use to create a loop
that runs commands in a command block while a specified condition evaluates to $true.
So this will run the statement in the scriptblock (Write-host) while $i is less than $gcls (the count of items found in get-childitem).
each time it loops, it willll print $($i): $($ls[$i]) to the console and then increase $i by 1 (the $i++ in the For)
breaking down the print statement:
$($i) - prints the current loop count. The $() is a subexpression operator. It isnt really needed here, but it isnt hurting anything see https://ss64.com/ps/syntax-operators.html
$($ls[$i]) - we have a subexpression operator again. This time were printing a value in the variable $ls. The [$i] gets an item from the array. We need the $(), otherwise it would print all the contents of $ls rather than just the one item we wanted - try it yourself write-host "$($ls[0])" vs write-host "$ls[0]"
$ls[0] would get the first item in the array
$ls[1] would get the second, so on and so forth. Can see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_arrays?view=powershell-7.1 for more info
#>
Write-host "$($i): $($ls[$i])"
}
$selection = Read-Host "Enter Number" #prompts the user for input, expects INT seperated by spaces - 5 10
$selection = $selection -split " " #splits the user input
$gc[$selection] #prints the names using user input above. If the user enters 5, theyd get the 5th item returned by Get-ChildItem. Entering "5 10" would get the 5th and the 10th item. Again, see about_arrays above

My code will not write "No files to process"

My code will not display "No files to process" on the screen. It is supposed to count the files in a directory and if there are none then it should display "No files to process" and then exit.
# Function Measure, counts files to see if there are any to process.
Function Measure
{
$Measure = ( Get-ChildItem C:\temp\BDMDump\ | Measure-Object ).Count
If ($Measure = "0")
{Write-Host "No files to process"|Exit}
else
{Write-Host "There are files to process.."}
}
I expect to see "No files to process".
There are 4 issues here:
You are using the '=' which is only used for assignment. Use '-eq for comparison.
You are enclosing your integer with quotes, converting it into a string. Just remove the quotes.
As mentioned by ineedalife, you're piping to Exit. You should instead use a semi-colon and the return keyword ; return to exit the function. Even this is probably not needed if the function doesn't do anything else.
You are trying to use "Measure" as the function's name. This is an alias to Measure-Object! Simply change the name to something else, like "Measure-Files"
Additionally, you could remove | Measure-Object because the object System.IO.FileInfo which is returned by Get-ChildItem already has a "Count" method.
Here's a revised copy of your code with all the changes:
Function Measure-Files {
$Measure = Get-ChildItem "C:\temp\BDMDump\"
If ($Measure.Count -eq 0)
{ Write-Host "No files to process"; return }
else
{ Write-Host "There are files to process.." }
}
There are 3 problems
1. Exit can not be Piped to. If you want to exit the session useExit-PSSession this will close the window.
2. "Is equal to" should be changed from = to -eq
3. "0" should be changed to 0 as it is an integer
If ($Measure -eq 0)
{Write-Host "No files to process"|Exit-PSSession}
else
{Write-Host "There are files to process.."}

Renaming one file (and nothing more than ONE file) using PowerShell

The problem
I constantly find myself in need of quick-method to rename a random file here and there while I work. I need to bring these filenames down to a structure compatible with web standards and some personal needs. A few examples below:
When I find I need
---------------------------------- -----------------------------------------
Welcome to the party.JPG welcome_to_the_party.jpg
Instructions (and some other tips) instructions_and_some_other_tips
Bar Drinks – The Best Recipes bar_drinks_the_best_recipes
La mañana del águila y el ratón la_manana_del_aguila_y_el_raton
Basically I need:
all uppercase characters to become lowercase
spaces to become underscore
some other special characters and diacritics for other languages to become their closest match (á is a, é is e, ç is c, and so on...)
Symbols like ( ) [ ] { } ' ; , to completely dissapear
Perhaps some replacements (optional) as: # = no; # = at or & = and
Not the question, but just FYI and you can see the big picture
I will be using a registry entry [HKEY_CLASSES_ROOT*\shell...] so I can call a batch file and/or a PowerShell Script by right-clicking the desired file, passing the argument information (the file in question) to the script that way.
My guesses
I have been looking closely at PowerShell Scripts, but I am not very knowledgeable about this area yet and all the solutions provided so far are addressing the entire folder (Dir/Get-ChildItem) instead of a specific file.
For example, I was successful using the line below (PowerShell) to replace all spaces by underscore, but then it affects other files in the directory as well.
Dir | Rename-Item –NewName { $_.name –replace “ “,”_“ }
Again, I do not need to address this problem for the entire folder, since I already have ways of doing so using software like Total Commander.
Thanks for any help you can give me.
Ruy
may be this code can help you
function Remove-Diacritics([string]$String)
{
$objD = $String.Normalize([Text.NormalizationForm]::FormD)
$sb = New-Object Text.StringBuilder
for ($i = 0; $i -lt $objD.Length; $i++) {
$c = [Globalization.CharUnicodeInfo]::GetUnicodeCategory($objD[$i])
if($c -ne [Globalization.UnicodeCategory]::NonSpacingMark) {
[void]$sb.Append($objD[$i])
}
}
return("$sb".Normalize([Text.NormalizationForm]::FormC))
}
function Clean-String([string]$String)
{
return(Remove-Diacritics ($String.ToLower() -replace "#", "no" -replace "\#", "at" -replace "&", "and" -replace "\(|\)|\[|\]|\{|\}|'|;|\,", "" -replace " ", "_"))
}
$youfile="C:\tmp4\121948_DRILLG.tif"
$younewnamefile=Clean-String $youfile
Rename-Item -Path $youfile $younewnamefile
Place this script somewhere (let's call it WebRename.ps1):
$old = $args -join ' '
$new = $old.ToLower().Replace(' ', '_')
# add all the remaining transformations you need here
Rename-Item $old $new
In the registry use this as the command (with your own path of course):
PowerShell -c C:\WebRename.ps1 "%1"
If your looking to be able to do this quickly and always want the same changes to be made you can add the following function to a .psm1 file and then place the file in one of your module folders (C:\Program Files\WindowsPowerShell\Modules is the most common one) you'll be able to just call WebRename-File filePath any time you need to quickly rename a file, the function is set up in such a way as to work fine if you pass in a single file path or you can pipe the results of a get-childitem to it if you ever do find the need to do bulk renames.
function WebRename-File {
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
$filePath
)
begin{}
Process{
foreach($path in $filePath){
$newPath = $path.ToLower()
$newPath = $newPath.Replace(' ','_')
###add other operations here###
Rename-Item -Path $path -NewName $newPath
}
}
end{}
}

Powershell CSV Import loop

I am trying to use Powershell to create a dhcpd.leases file for an ISC DHCP Server. I have imported an array in the variable $lease_import and have run the following code.
Beware; This code is not pretty (at all) and does not intend to be. (It is a one time use type of deal)
$input_file= Get-InputFile
$lease_import = ipcsv -Path $file_path -Delimiter "," | where {
($_.binding_state -ne "ABANDONED") -and ($_.mac_address -ne "") -and ($_.ends -match "2014")
}
$output_file = $($input_file | Split-Path) + "\dhcpd.leases"
if (Test-Path $output_file){
ri $output_file
}
ac $output_file "# The format of this file is documented in the dhcpd.leases(5) manual page.`n# This lease file was written by isc-dhcp-4.2.4-P2`n"
$end_time = $($(Get-Date).AddDays(7).ToString('yyyy-MM-dd') +" "+ $(Get-Date -Format T) -replace "-","/")
$lease_import | foreach {
ac $output_file "lease $($_.ip_address) {`n starts $($($($_.starts -replace ".......$","00") -replace "T"," ") -replace "-","/");`n ends $end_time;`n hardware ethernet $($_.mac_address);`n binding state active;`n}`n"
}
ac $output_file 'server-duid "\000\001\000\001\025\034G\301\000\000^\000\001\002";'
Running this code will generate a statement as follows:
lease 10.116.3.9 {
starts 2014/10/31 09:59:00;
ends 2014/11/09 17:01:21;
hardware ethernet 00:1c:c4:96:bc:50;
binding state active;
}
For each row in the array. Problems occur after running this script. Every statement as shown above occurs twice in dhcpd.leases. I have looked over this piece of code for hours on end but I can't figure out why this is happening. The imported CSV file does not have cloned rows. Each row is unique.
Am I overlooking something ?
EDIT: The above code would work when importing it into a DHCP server as double entries simply get applied twice, but I'd really like to figure out why they occur twice in dhcpd.leases in the first place.

Trying to run legacy executables from powershell script

I am looking to run net.exe from a script and I am having some trouble with spaces. Here is the code...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
net.exe use "\\"$_.Name '/user:'$_.Name'\Administrator' $_.Pass
$sqlcheck = sc.exe \\$gsssql[1] query "WUAUSERV"
}
When I set line 5 to Write-Host I see that there are spaces that are added outside of anywhere I have quotes which is breaking the net.exe command. How can I remove those spaces?
For anyone questioning how I am doing this, the net.exe command is the only way I can get to these machines as WMI is blocked in this enclave.
My first guess is that you've got "invisible" spaces in your CSV file. For example their is likely a trailing whitespace after the names of your servers in the CSV that your eyes of course don't see. You can fix that either by fixing the CSV file, or using .Trim() on your imported strings -- i.e. $_.Name.Trim()
If that's not the case, or not the only issue, then this is something I've had issues with to. When I have complicated strings like your desired net.exe arguments I've liked to take precautions and get extra pedantic with defining the string and not rely on PowerShell's automatic guessing of exactly where a string begins and ends.
So, instead of baking your parameters inline on your net.exe command line hand-craft them into a variable first, like so
$args = '\\' + $_.name + '/user:' + $_.name + '\Administrator' + $_.pass
If you write-Host that out you'll see that it no longer has your rogue spaces. Indeed you may notice that it no longer has enough spaces, so you'll have to get a little explicit about where they belong. For instance the above line doesn't put the proper spaces between \\servername and /user, or between the username and password, so you'd have to add that space back in, like so.
$args = '\\' + $_.name + ' /user:' + $_.name + '\Administrator ' + $_.pass
Notice the explicit spaces.
I finally resolved this myself using #EdgeVB's solution. The code ended up like this...
# Variables
$gssservers = Import-Csv "gssservers.csv"
$gssservers | Where-Object {$_.Tier -match "DB"} | Foreach-Object {
$cmd1 = 'use'
$arg1 = '\\' + $_.Name
$arg2 = ' /user:' + $_.Name + '\Administrator '
& net.exe $cmd1 $arg1 $arg2 $_Pass
$cmd2 = 'query'
$svc1 = 'mssqlserver'
& sc.exe $arg1 $cmd2 $svc1 | Write-Host
}
Not only do you need to bake the variables in beforehand, but they also cannot cross certain thresholds (for instance, if "use" and "\" are in the same variable, it breaks.