Human readable byte script in Powershell - windows

Im trying to write a script where the user can input a number and the script will convert it into human readable bytes.
Heres what I´ve got:
# human-readable-byte.ps1
$ans = Read-Host
if ($ans -gt 1TB) {
Write-Host ($ans/1TB) "TB"
} elseif ($ans -gt 1GB) {
Write-Host ($ans/1GB) "GB"
} elseif ($ans -gt 1MB) {
Write-Host ($ans/1MB) "MB"
} elseif ($ans -gt 1KB) {
Write-Host ($ans/1KB) "KB"
} else {
Write-Host $ans "B"
}
The problem i get is that everything under 2.0 comes out in B but then everything over comes out in TB. Why? Seems like everything inbetween is ignored. ive tried to do this in many different ways, but cant get it to work.
Any ideas?

Operators in PowerShell convert the operands to the type of the left operand. So in your case the comparisons convert the number on the right to a string. And thus -gt does a string comparison.
You'd need to convert $ans to the right type:
[long]$ans = Read-Host
or swap the operands:
if (1TB -lt $ans) ...

Related

If statement for OS version

I do not get this to work and am I really confused why it is not working. My OS version is 10.0.14393 but it still prints that it is lower than 6? What is wrong here?
$verCheckOS = (Get-WmiObject Win32_OperatingSystem).Version
if ($verCheckOS -lt 6) {
Write-Host -ForeGroundColor Red "Version is too low, $verCheckOS"
} else {
Write-Host -ForegroundColor Green "OS version is good, $verCheckOS"
}
This below with simple numbers work good so I do not know if the OS-version number is some specific type of datatype? How do I compare the OS version number with an if statement?
$value = 10
if ($value -lt 6) {
Write-Host "Value is lower than 6"
} else {
Write-Host "Value is greater than 6"
}
If you typecast the $verCheckOS variable, then something more interesting will happen:
[version]$verCheckOS = (Get-WmiObject win32_operatingsystem).version
When running the code you provided, we get an error:
if($verCheckOS -lt 6) { write-host -ForeGroundColor Red "Version is too low, $verCheckOS" }
Else { write-host -ForegroundColor Green "OS version is good, $verCheckOS" }
Could not compare "10.0.17134" to "6". Error: "Cannot convert value "6" to type "System.Version".
Error: "Version string portion was too short or too long.""
At line:1 char:8
What happens here is the variable $verCheckOS is now a [version] datatype, and the lone integer 6 cannot be converted into a [version] datatype. It requires at least 2 octets to be able to be converted, as the [version] datatype is represented like so:
Major Minor Build Revision
----- ----- ----- --------
10 0 17134 -1
There is a number of ways to deal with this depending on what number you're trying to compare. If you're simply looking to compare the 'Major' version octet, you can compare the integers as opposed to the versions:
if($verCheckOS.Major -lt 6) { write-host -ForeGroundColor Red "Version is too low, $verCheckOS" }
Else { write-host -ForegroundColor Green "OS version is good, $verCheckOS" }
This will return a positive result, by extrapolating the 'Major' version number and making an integer to integer comparison.
If you want to compare real 'versions', then you'll need to provide the comparison with 2 version objects, here's an example:
[version]$verCheckOS = (Get-WmiObject win32_operatingsystem).version
[version]$verCompare = "6.0"
if($verCheckOS -lt $verCompare) { write-host -ForeGroundColor Red "Version is too low, $verCheckOS" }
Else { write-host -ForegroundColor Green "OS version is good, $verCheckOS" }
To see the conversion from string to version happen in action or to try parse your own version numbers, use the [version] .NET class like so:
[version]::new("6.0")
(Get-WmiObject win32_operatingsystem).version returns a string (pipe to Get-Member to verify), so $verCheckOS -lt 6 performs lexical comparison:
PS> '10.0.14393' -lt 6 # *lexical* comparison; 6 is coerced to a string
True # !! lexically, '1' comes before '6'
Casting to [version] allows you to perform the intended numerical comparison based on the .Major property:
PS> $verCheckOS = [version] (Get-WmiObject win32_operatingsystem).version
PS> $verCheckOS.Major -lt 6 # 10 -lt 6 - numerical comparison
False # OK
Adam Parson's helpful answer shows that it's also possible to meaningfully compare two [version] instances as a whole with -lt, which allows for more fine-grained comparison logic.

Kaldi librispeech data preparation error

I'm trying to do ASR system. Im using kaldi manual and librispeech corpus.
In data preparation step i get this error
utils/data/get_utt2dur.sh: segments file does not exist so getting durations
from wave files
utils/data/get_utt2dur.sh: could not get utterance lengths from sphere-file
headers, using wav-to-duration
utils/data/get_utt2dur.sh: line 99: wav-to-duration: command not found
And here the piece of code where this error occures
if cat $data/wav.scp | perl -e '
while (<>) { s/\|\s*$/ |/; # make sure final | is preceded by space.
#A = split;
if (!($#A == 5 && $A[1] =~ m/sph2pipe$/ &&
$A[2] eq "-f" && $A[3] eq "wav" && $A[5] eq "|")) { exit (1); }
$utt = $A[0]; $sphere_file = $A[4];
if (!open(F, "<$sphere_file")) { die "Error opening sphere file $sphere_file"; }
$sample_rate = -1; $sample_count = -1;
for ($n = 0; $n <= 30; $n++) {
$line = <F>;
if ($line =~ m/sample_rate -i (\d+)/) { $sample_rate = $1; }
if ($line =~ m/sample_count -i (\d+)/) { $sample_count = $1;
}
if ($line =~ m/end_head/) { break; }
}
close(F);
if ($sample_rate == -1 || $sample_count == -1) {
die "could not parse sphere header from $sphere_file";
}
$duration = $sample_count * 1.0 / $sample_rate;
print "$utt $duration\n";
} ' > $data/utt2dur; then
echo "$0: successfully obtained utterance lengths from sphere-file headers"
else
echo "$0: could not get utterance lengths from sphere-file headers,
using wav-to-duration"
if command -v wav-to-duration >/dev/null; then
echo "$0: wav-to-duration is not on your path"
exit 1;
fi
In file wav.scp i got such lines:
6295-64301-0002 flac -c -d -s /home/tinin/kaldi/egs/librispeech/s5/LibriSpeech/dev-clean/6295/64301/6295-64301-0002.flac |
In this dataset i have only flac files(they downloaded via provided script) and i dont understand why we search wav-files? And how run data preparation correctly(i didnt change source code in this manual.
Also, if you explain to me what is happening in this code, then I will be very grateful to you, because i'm not familiar with bash and perl.
Thank you a lot!
The problem I see from this line
utils/data/get_utt2dur.sh: line 99: wav-to-duration: command not found
is that you have not added the kaldi tools in your path.
Check the file path.sh and see if the directories that it adds to your path are correct (because it has ../../.. inside and it might not match your current folder setup)
As for the perl script, it counts the samples of the sound file and then it divides with the sample rate in order to get the duration. Don't worry about the 'wav' word, your files might be on another format, it's just the name of the kaldi functions.

Increase performance for checking file delimiters

After spending some time looking for the most clearcut way to check if the body of a file has the same amount of delimiters as the header I came up with this code:
Param #user enters the directory path and delimiter they are checking for
(
[string]$source,
[string]$delim
)
#try {
$lineNum = 1
$thisOK = 0
$badLine = 0
$noDelim = 0
$archive = ("*archive*","*Archive*","*ARCHIVE*");
foreach ($files in Get-ChildItem $source -Exclude $archive) #folder directory may have sub folders, as a temp workaround just made sure to exclude any folder with archive
{
$read2 = New-Object System.IO.StreamReader($files.FullName)
$DataLine = (Get-Content $files.FullName)[0]
$validCount = ([char[]]$DataLine -eq $delim).count #count of delimeters in the header
$lineNum = 1 #used to write to host which line is bad in file
$thisOK = 0 #used for if condition to let the host know that the file has delimeters that line up with header
$badLine = 0 #used so the write-host doesnt meet the if condition and write the file is ok after throwing an error
while (!$read2.EndOfStream)
{
$line = $read2.ReadLine()
$total = $line.Split($delim).Length - 1;
if ($total -eq $validCount)
{
$thisOK = 1
}
elseif ($total -ne $validCount)
{
Write-Output "Error on line $lineNum for file $files. Line number $lineNum has $total delimeters and the header has $validCount"
$thisOK = 0
$badLine = 1
break; #break or else it will repeat each line that is bad
}
$lineNum++
}
if ($thisOK = 1 -and $badLine -eq 0 -and $validCount -ne 0)
{
Write-Output "$files is ok"
}
if ($validCount -eq 0)
{
Write-Output "$files does not contain entered delimeter: $delim"
}
$read2.Close()
$read2.Dispose()
} #end foreach loop
#} catch {
# $ErrorMessage = $_.Exception.Message
# $FailedItem = $_.Exception.ItemName
#}
It works for what I have tested so far. However, when it comes to larger files, it takes considerably longer. I was wondering what I can do or change for this code to make it process these text/CSV files more quickly?
Also, my try..catch statements are commented out since the script doesn't seem to run when I include them - no error just enters a new command line. As a thought I was looking to incorporate a simple GUI for other users to double check.
Sample file:
HeaderA|HeaderB|HeaderC|HeaderD //header line
DataLnA|DataLnBBDataLnC|DataLnD|DataLnE //bad line
DataLnA|DataLnB|DataLnC|DataLnD| //bad line
DataLnA|DataLnB|DataLnC|DataLnD //good line
Now that I look at it, I guess there could be an issue where there are the correct amount if delimeters but the columns mismatch like this:
HeaderA|HeaderB|HeaderC|HeaderD
DataLnA|DataLnBDataLnC|DataLnD|
The main problem that I see is that you are reading the file twice -- once with the call to Get-Content, which reads the entire file into memory, and a second time with your while loop. You can double the speed of your process by replacing this line:
$DataLine = (Get-Content $files.FullName)[0] #inefficient
with this:
$DataLine = Get-Content $files.FullName -First 1 #efficient

How to remove partial path from Get-Location output?

I'm trying to write a custom prompt for PowerShell and I was wondering how I would filter out the 1...n directories in the output of Get-Location.
function prompt {
"PS " + $(get-location) + "> "
}
So, if the path is too long I would like to omit some of the directories and just display PS...blah\blah> or something. I tried (get-container) - 1 but it doesn't work.
Use Split-Path with the -Leaf parameter if you want just the last element of a path:
function prompt {
"PS {0}> " -f (Split-Path -Leaf (Get-Location))
}
I wanted to make a more dynamic function. I do just basic string manipulation. You could do some logic nesting Split-Path but the string manipulation approach is just so much more terse. Since what you want to be returned wont be a fully validated path I feel better offering this solution.
Function Get-PartialPath($path, $depth){
If(Test-Path $path){
"PS {0}>" -f (($path -split "\\")[-$depth..-1] -join "\")
} else {
Write-Warning "$path is not a valid path"
}
}
Sample Function call
Get-PartialPath C:\temp\folder1\sfg 2
PS folder1\sfg>
So you can use this simple function. Pass is a string for the path. Assuming it is valid then it will carve up the path into as many trailing chunks as you want. We use -join to rebuild it. If you give a $depth number that is too high the whole path will be returned. So if you only wanted to have 3 folders being shown setting the $depth for 3.
Ansgar Wiechers' answer will give you the last directory but if you want a way to do multiple directories at the end of the filepath (using the triple dot notation) you can cast the directory path to a uri and then just get and join the segments:
function prompt {
$curPath = pwd
$pathUri = ([uri] $curPath.ToString())
if ($pathUri.Segments.Count -le 3) {
"PS {0}>" -f $curPath
} else {
"PS...{0}\{1}>" -f $pathUri.Segments[-2..-1].trim("/") -join ""
}
}
Or using just a string (no uri cast)
function prompt {
$curPath = pwd
$pathString = $curPath.Tostring().split('\') #Changed; no reason for escaping
if ($pathString.Count -le 3) {
"PS {0}>" -f $curPath
} else {
"PS...{0}\{1}>" -f $pathString[-2..-1] -join ""
}
}
$a = prompt
Write-Host $a
Then just change -2 to whatever you want to be the first directory and -le 3 to match. I typically use the uri cast when I have to run stuff through a browser or over connections to Linux machines (as it uses "/" as a path separator) but there is no reason to not use the string method for normal operations.

Cleaning AD from import-CSV - process line by line or another way/functions - improving code

I've got a CSV-file from HR with aprox 1000 lines (employees) that I feed to AD with Powershell.
This works, but I am a bit uncertain if I am doing this the right way.
This are my major concerns:
I am setting the attributes one at a time. Should I put the "changes" into an some kind of array/hasthable/object and do it all at once at the end of the script? But How? "New-Object"?
Should I use functions? But how can I return values (and continue based on the result from the function)?
All programming hints, corrections would be GREATLY appreciated. I really understand this wonderful community of knowledgable people so, let me have it. If you have the time please tell me how I can do this better..
This is my code:
Add-PSSnapin Microsoft.Exchange.Management.PowerShell.Admin -ErrorAction silentlycontinue
Add-PSSnapin quest.activeroles.admanagement -ErrorAction silentlycontinue
$file = "\Scripts\employees.csv" # Location of the input file
$file2 = "\Scripts\employees2.csv" # Temp file
$logfile = "\Scripts\logfile.txt" # log file
remove-item $logfile -Force -ErrorAction SilentlyContinue
Get-Content $file | Out-File -Encoding UTF8 $file2 # Convert to UTF8 (we don't touch the original inputfile)
$ListEmployees = Import-Csv $file2 -Delimiter ";" # Import the file to CSV
foreach ($ListEmployee in $ListEmployees) {
$ListDisplayName = $ListEmployee.firstname + " " + $ListEmployee.lastname
if($ADemployee = Get-QADUser -displayname $ListDisplayName -IncludedProperties employeeid )
{
## CHECK NAME
if($($ADEmployee.displayname) -eq $($ListDisplayName))
{
echo "MATCH: $($ADEmployee.displayname)"
}
## CHECK COMPANY
if($($ADEmployee.company) -ne $($ListEmployee.company))
{
echo " CHANGE - Company: '$($ADEmployee.company)' to '$($ListEmployee.company)'"
Set-QADUser -identity $($ADEmployee.samaccountname) -Company $($ListEmployee.company) -WhatIf
}
else
{
echo " OK - Company : no change '$($ListEmployee.company)'"
}
## CHECK OFFICE
if($($ADEmployee.office) -ne $($ListEmployee.office))
{
echo " CHANGE - Office '$($ADEmployee.office)' to '$($ListEmployee.office)'"
Set-QADUser -identity $($ADEmployee.samaccountname) -Office $($ListEmployee.Office) -WhatIf
}
else
{
echo " OK - Office : no change '$($ListEmployee.office)'"
}
## CHECK MOBILE
if( $listemployee.mobile -match '\S' )
{
if($($ADEmployee.mobile) -ne $($ListEmployee.mobile))
{
echo " CHANGE - Mobile : '$($ADEmployee.mobile)' to '$($ListEmployee.mobile)'"
Set-QADUser -identity $($ADEmployee.samaccountname) -Mobile $($ListEmployee.mobile) -WhatIf
}
else
{
echo " OK - Mobile : no change '$($ListEmployee.mobile)'"
}
}
## CHECK EMPLOYEEID
if($($ADEmployee.employeeid) -ne $($ListEmployee.employeeid))
{
echo " CHANGE - EmployeeID: '$($ADEmployee.employeeid)' to '$($ListEmployee.employeeid)'"
Set-QADUser -identity $($ADEmployee.samaccountname) -ObjectAttributes #{employeeID = $($ListEmployee.employeeid)} -WhatIf
}
else
{
echo " OK - EmployeeID : no change '$($ListEmployee.employeeid)'"
}
$match++
}
else
{
if($EXContact = Get-Contact $ListDisplayName -ErrorAction SilentlyContinue)
{
echo "MATCH CONTACT: $ListDisplayName (contact)"
## CHECK MOBILE
if( $listemployee.mobile -match '\S' )
{
if($($EXContact.Mobilephone) -ne $($ListEmployee.mobile))
{
echo " CHANGE - Mobile : '$($EXContact.Mobilephone)' to '$($ListEmployee.mobile)'"
}
else
{
echo " OK - Mobile ; No change ($($ListEmployee.mobile))"
}
}
## CHECK COMPANY
if($($EXContact.company) -ne $($ListEmployee.company))
{
echo " CHANGE - Company: '$($EXContact.company)' to '$($ListEmployee.company)'"
}
else
{
echo " OK - Company : No change($($ListEmployee.company))"
}
## CHECK OFFICE
if($($EXContact.office) -ne $($ListEmployee.office))
{
echo " CHANGE - Office '$($EXContact.office)' to '$($ListEmployee.office)'"
}
else
{
echo " OK - Office : No Change($($ListEmployee.office))"
}
$contactmatch++
}
else
{
echo "$ListDisplayName" | Out-File $logfile -Append
echo "NO MATCH: $ListDisplayName"
$nomatch++
}
}
$i++
}
echo " "
echo "List contains $i accounts"
echo "Accounts: $match matches"
echo "Contacts: $contactmatch"
echo "No Match: $nomatch"
And; If you think this is cr*p, tell me! I'd rather hear it from you than you staying silent just to be polite! I am "quite" new to this so I deserve it:)
Something that seems odd about the whole thing is using display name as your identity reference. As an identity reference, it't both volatile and potentially ambiguos in AD, and seems a poor choice to use to drive a maintenance script.
Here is my opinion :
1) I really think that the problem #mjolinor point is important, and you will meet troubles (I mean need human check) if you don't use one of the identity attributes fixed by Microsoft (samAccountName, userPrincipalName or better objectGuid, objectSid ...) as a key to find your users in Active-Directory.
If it's not possible you perhaps can buid a filter on the top of multiples attributes. If you CSV comes from another LDAP Directory you perhaps can integrate their unique ID in you Schema (in this case see Microsoft Services for UNIX 3.5 (MSFU3.5) schema extensions to Active Directory).
2) Once you find one of your CSV entry in your Active-Directory, you check each attributes, and then replace 'one by one' the ones in your AD with the one in your CSV.
Here my advice will be to check all the differencies between your CSV and AD entry, and them made an unique change into the Directory. In fact, on one différence, I will change them all in one command. I don't know how Set-QADUser is written, but in the low level layers all the attributes replacement can be made one shot (LDAP_REPLACE, or in a single ADSI commit)
3) Just a remark : begining PowerShell V2 (Seven, W2K8) an Active-Directory module is given by Microsoft.

Resources