If statement for OS version - windows

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.

Related

Powershell IF conditional isn't firing in the way I expected. Unsure what I'm doing wrong

I am writing a simple script that makes use of 7zip's command-line to extract archives within folders and then delete the original archives.
There is a part of my script that isn't behaving how I would expect it to. I can't get my if statement to trigger correctly. Here's a snippet of the code:
if($CurrentRar.Contains(".part1.rar")){
[void] $RarGroup.Add($CurrentRar)
# Value of CurrentRar:
# Factory_Selection_2.part1.rar
$CurrentRarBase = $CurrentRar.TrimEnd(".part1.rar")
# Value: Factory_Selection_2
for ($j = 1; $j -lt $AllRarfiles.Count; $j++){
$NextRar = $AllRarfiles[$j].Name
# Value: Factory_Selection_2.part2.rar
if($NextRar.Contains("$CurrentRarBase.part$j.rar")){
Write-Host "Test Hit" -ForegroundColor Green
# Never fires, and I have no idea why
# [void] $RarGroup.Add($NextRar)
}
}
$RarGroups.Add($RarGroup)
}
if($NextRar.Contains("$CurrentRarBase.part$j.rar")) is the line that I can't get to fire.
If I shorten it to if($NextRar.Contains("$CurrentRarBase.part")), it fires true. But as soon as I add the inline $j it always triggers false. I've tried casting $j to string but it still doesn't work. Am I missing something stupid?
Appreciate any help.
The issue seems to be your for statement and the fact that an array / list is zero-indexed (means they start with 0).
In your case, the index 0 of $AllRarfiles is probably the part1 and your for statement starts with 1, but the file name of index 1 does not contain part1 ($NextRar.Contains("$CurrentRarBase.part$j.rar"), but part2 ($j + 1).
As table comparison
Index / $j
Value
Built string for comparison (with Index)
0
Factory_Selection_2.part1.rar
Factory_Selection_2.part0.rar
1
Factory_Selection_2.part2.rar
Factory_Selection_2.part1.rar
2
Factory_Selection_2.part3.rar
Factory_Selection_2.part2.rar
3
Factory_Selection_2.part4.rar
Factory_Selection_2.part3.rar
Another simpler approach
Since it seems you want to group split RAR files which belong together, you could also use a simpler approach with Group-Object
# collect and group all RAR files.
$rarGroups = Get-ChildItem -LiteralPath 'C:\somewhere\' -Filter '*.rar' | Group-Object -Property { $_.Name -replace '\.part\d+\.rar$' }
# do some stuff afterwards
foreach($rarGroup in $rarGroups){
Write-Verbose -Verbose "Processing RAR group: $($rarGroup.Name)"
foreach($rarFile in $rarGroup.Group) {
Write-Verbose -Verbose "`tCurrent RAR file: $($rarFile.Name)"
# do some stuff per file
}
}

Powershell - Extract timestamp

i am completely new to Powershell and I am trying to create a script to extract the timestamp for the string found in a log file. There are log files everyday and it should select the latest file. It then looks for a string in that file. Now, where it finds the string it should also return a timestamp where the string is located. I am only able to complete the first part and struggling with the 2nd bit. My script is in no way optimal but it is a start. Any help is appreciated.
Set-Location -Path "O:\xyz\0502251\logs"
$latest = (Get-ChildItem -Path $dir -Filter 'async_SPARKLE_ONITE_*.log' | Sort-Object CreationDate -Descending | Select-Object -last 1).fullname
$pattern = "Successfulyl finished running psp_dba_maint_4"
$search = (Get-Content $latest | Select-String -Pattern "psp_dba_maint_4" | Select-Object -last 1)
if($search)
{
"The overnight completed at:"
}
else
{
"Do something"
}
This looks for the latest file and extracts the string. Now Just want to know at what time this log was generated.
The log is:
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on rptSPARKLE from host da-pvrep05 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPRK_SVC_IRISS logged in at 23:55:39 running SRI.exe:SELECT on rptSPARKLE from host da-pvrep02 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 126
Procedure 'psp_dba_locklogin'
Message String: -----> login SPARKLE_CAI logged in at 23:55:37 running SPARKLE ESB (pooled):AWAITING COMMAND on A_uatSPK from host DA-UVESB01 is still active
Connection 1
Server message: 2022-10-15 23:56:07:020
Message number: 0, Severity 10, State 1, Line 355
Procedure 'psp_dba_dbrefresh'
Message String: Setting PrivShield on SPRKSZCBS OUT OF maintenance mode
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 362
Procedure 'psp_dba_dbrefresh'
Message String:
Connection 1
Server message: 2022-10-15 23:56:07:022
Message number: 0, Severity 10, State 1, Line 364
Procedure 'psp_dba_dbrefresh'
Message String: - Refresh 2 complete
Connection 1
Server message: 2022-10-15 23:56:07:028
Message number: 0, Severity 10, State 1, Line 42
Procedure 'psp_dba_maint_4'
Message String: Successfulyl finished running psp_dba_maint_4
This has different values in files generated everyday. So it should look for the latest file and return the result as The overnight completed at: 23:56.
Here is a variant using the switch statement to efficiently process the file line-by-line and test each line to match given RegEx patterns:
$found = switch -File $latest -RegEx {
'\d\d:\d\d' { $timestamp = $matches[0] }
'Successfulyl finished running psp_dba_maint_4' { $true; break }
}
if( $found ) {
"The overnight completed at: $timestamp"
}
else {
"Not found"
}
$found = switch ... assigns the output of the switch statement to the variable. The output will be $true if the pattern has been found.
\d\d:\d\d searches for the time (first occurence of a pair of two digits separated by :) within the current line. The automatic variable $matches[0] then gives the matched value, similar to the RegEx -match operator. For a detailed explanation of the RegEx pattern and the ability to experiment with it, see this Regex101.com page.
Although there is no $false output of the switch statement, an empty output evaluates to $false in a boolean context (in this case the if statement), so if the pattern isn't found, the else branch will be entered.
There is a typo in Successfulyl which I just copied from your example ;)
To complement zett42's helpful switch-based answer:
A Select-String solution, as you attempted, is possible, via the -Context parameter, which allows you to capture the lines surrounding a matching line as well:
$time =
Select-String -LiteralPath $latest -Pattern 'psp_dba_maint_4' -Context 3 |
Select-Object -Last 1 |
ForEach-Object {
# Extract and output the 'HH:mm' part of the timestamp from
# the 3rd line above the match (the first element in the pre-context array)
$_.Context.PreContext[0] -replace '^.+ (..:..).+$', '$1'
}
if ($time) {
"The overnight completed at: $time"
} else {
'Do something'
}
Note:
It is much more efficient to pass the file path of the target file to Select-String, via -LiteralPath, than it is to pass the file's content, line by line, via Get-Content.
If you had assigned the whole file-info object (System.IO.FileInfo), as emitted by Get-ChildItem, to $latest, you could alternatively have provided it via the pipeline: $latest | Select-String ...
The type of the match-information objects that Select-String emits is Microsoft.PowerShell.Commands.MatchInfo.
The regex-based -replace operator is used to extract (part of) the time-of-day string from the context line.
For an explanation of the above regex (and substitution, where $1 refers to the first and only capture group, (...)), see this regex101.com page.
As you need to get the Time information only related to the procedure psp_dba_maint_4 you can do:
#Remove empty lines, join strings and split string at "connection 1" = you keep the related information together. Next parse the array of strings for the string which matches "psp_dba_maint_4" and after that extract dateTime
$null = (((Get-Content $latest | ?{$_}) -join $null) -split "connection 1" | ?{$_ -match "psp_dba_maint_4"}) -match '\d{2}:\d{2}:\d{2}'
$Search = $matches[0]
if($search){
"The overnight completed at: $search"
}
else{
"Do something"
}
Alternatively you could parse the log an create objects, e.g.:
$obj = #(
Get-Content $latest | ?{$_} | %{
#If string is connection 1 initialize hashtable/create object
If ($_ -match 'connection 1'){
If ($attrsHT){
new-object -typename psobject -Property $attrsht
}
$attrsHt = #{}
}
Else {
#replace Procedure with Procedure, replace ": " with | and split at |
$split = (($_ -replace "Procedure","Procedure:") -replace ": ","|") -split "\|"
#add key and value to hashtable
$attrsht.add($split[0],$split[1])
}
}
)
gives you the array $obj containing the loginformation:
$obj[0] | fl *
Procedure : 'psp_dba_locklogin'
Message String : -----> login SPRK_SVC_IRISS logged in at 23:55:45 running SRI.exe:AWAITING COMMAND on
rptSPARKLE from host da-pvrep02 is still active
Server message : 2022-10-15 23:56:07:020
Message number : 0, Severity 10, State 1, Line 126
Here's another solution to add to the rest.
if ($found = Get-Content -Path $latest -ReadCount 7 | %{
if ($_ -match 'Setting PrivShield on SPRKSZCBS OUT OF maintenance mode')
{
[regex]::Match($_,'\d\d:\d\d').Value
}
}
) { "The overnight completed at: $found" }
else { 'not found' }
On another note, $latest contains the oldest file and not the newest. Specifying -Descending in your Sort-Object brings the most current files to the top. So, when you select the last one via -Last 1, that is the oldest file and not the newest.

Human readable byte script in Powershell

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) ...

How to display the Current Month in Plain Text Format?

Any suggestions on how I can quickly print the current month of a calendar in plain text to the clipboard or to the command in the a windows environment? To be clear here, I would like to see the full printed month, similar to what you see when you single click on the clock in the windows taskbar. I'm thinking along the lines of a a lightweight PowerShell script or perhaps there is some other pre-packaged Windows application functionality that would allow me to do this easily.
Give this a try:
function Get-Calendar($year=(Get-Date).Year,$month=(Get-Date).Month){
$dtfi = New-Object System.Globalization.DateTimeFormatInfo
$AbbreviatedDayNames=$dtfi.AbbreviatedDayNames | ForEach-Object {" {0}" -f $_.Substring(0,2)}
$header= "$($dtfi.MonthNames[$month-1]) $year"
$header=(" "*([math]::abs(21-$header.length) / 2))+$header
$header+=(" "*(21-$header.length))
Write-Host $header -BackgroundColor yellow -ForegroundColor black
Write-Host (-join $AbbreviatedDayNames) -BackgroundColor cyan -ForegroundColor black
$daysInMonth=[DateTime]::DaysInMonth($year,$month)
$dayOfWeek =(New-Object DateTime $year,$month,1).dayOfWeek.value__
$today=(Get-Date).Day
for ($i = 0; $i -lt $dayOfWeek; $i++){Write-Host (" "*3) -NoNewline}
for ($i = 1; $i -le $daysInMonth; $i++)
{
if($today -eq $i){Write-Host ("{0,3}" -f $i) -NoNewline -BackgroundColor red -ForegroundColor white}
else {Write-Host ("{0,3}" -f $i) -NoNewline -BackgroundColor white -ForegroundColor black}
if ($dayOfWeek -eq 6) {Write-Host}
$dayOfWeek = ($dayOfWeek + 1) % 7
}
if ($dayOfWeek -ne 0) {Write-Host}
}
PS> Get-Calendar
January 2013
Su Mo Tu We Th Fr Sa
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
A calendar (a system for breaking a linear date into day/month/year) doesn't have a current anything.
But if you mean get the month of a DateTime in a given calendar you need to look at specifying a CultureInfo which in .NET incorporates a selected calendar:
$d = Get-Date
$d.ToString('MMMM', [System.Globalization.CultureInfo]::CurrentCulture)
There are a number of ways to create a custom CultureInfo or (more generally) an instance of DateTimeFormatInfo and passing as the second parameter of DateTime.ToString().
The simplest approach is to get an instance of CultureInfo that uses the target calender, eg.:
$ci = [System.Globalization.CultureInfo]::CreatedSpecifiedCulture('jp-jp')
MSDN has a whole page on working with calendars: http://msdn.microsoft.com/en-us/library/82aak18x%28v=vs.100%29.aspx (this is the .NET 4 version for PowerShell 3).
new-alias Out-Clipboard $env:SystemRoot\system32\clip.exe
(get-date -Format MMMM) |Out-Clipboard

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