PowerShell script issue operators - powershell

I am attempting to use PowerShell to detect Intel NIC drivers, prior to deploying the updated drivers. I changed my script a bit to troubleshoot, to make sure I am capturing the correct driver versions, and eventually verifying proper use of operators (which I may not be).
#Change $DeployVersion to the driver version being deployed
#Change $INTELNICMODEL to the model of Intel NIC
#=======================================================
$DeployVersion = “12.15.31.0″
$INTELNICMODEL = “82579LM”
#Get IntelNIC Driver Version from Win32_PnPSignedDriver
$CurrentlyInstalledDriverVersion = Get-WmiObject Win32_PnPSignedDriver | Where-Object {$_.deviceclass -match “NET” -and $_.devicename -like “*$INTELNICMODEL*” -and $_.driverversion} | Select driverversion
Write $CurrentlyInstalledDriverVersion.driverversion
Write $DeployVersion
If ($CurrentlyInstalledVersion.driverversion -lt $DeployVersion)
{Write “need to update driver”}
else
{Write “driver is current”}
From a computer with $CurrentlyInstalledDriverVersion = $DeployVersion
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
need to update driver
Clearly, this is not right, so some testing…
change -lt to -gt
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
driver is current
…and then try -eq
OUTPUT
PS> .\testIntelNIC.ps1
12.15.31.0
12.15.31.0
driver is current
Then from a computer where $CurrentlyInstalledDriverVersion < $DeployVersion
OUTPUT
PS> .\testIntelNIC.ps1
12.6.47.1
12.15.31.0
need to update driver
..which looks good, but…
Change -lt to -gt
OUTPUT
PS> .\testIntelNIC.ps1
12.6.47.1
12.15.31.0
driver is current
Am I using the operators incorrectly? Or the if/else?

String comparisons don't know anything about the inner structure of your version strings. Cast the strings to Version objects and you'll be able to do proper comparisons. Note that you need to expand the DriverVersion property for the conversion to work.
[Version]$DeployVersion = "12.15.31.0"
...
[Version]$CurrentlyInstalledDriverVersion = Get-WmiObject Win32_PnPSignedDriver |
Where-Object {
$_.deviceclass -match "NET" -and
$_.devicename -like "*$INTELNICMODEL*" -and
$_.driverversion
} | Select -Expand driverversion

Related

Calculate days since last program installed using PowerShell

What is the way to find all recently installed/repaired/modified software?
What I have been trying is to get the list from the registry, then filter it to find only relevant programs, and then try to calculate which of them was installed before a certain threshold.
This is what I have been doing:
$Installed_Software=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate
$SoftwareInstall_Days = 40
$TheDate = (([datetime]::Now))
$Installed_recently= #()
$Installed_recently=($Installed_Software |
Where-Object {($_.DisplayName -match ('Something') -or $_.Publisher -match ('SomeOtherThing'))} |
where (($TheDate - $_.InstallDate) -le $SoftwareInstall_Days)
if ($Installed_recently) {
Write-Output "Relevant software was recently installed and/or repaired / modified.`nThese are the items:`n$Installed_recently"
}
else {
Write-Output "No relevant software was recently installed and/or repaired / modified."
}
Of course, the following condition is just an illustration of what I am failing to do:
where (($TheDate - $_.InstallDate) -le $SoftwareInstall_Days)
How can this be done?
Am I even in the right direction here?
In order to be able to compare dates, you need to parse the InstallDate string that you get from registry and then subtract it from the current date.
There are couple of formats that this string can be. On my machine, English Win10 x86, I see 2 date formats: yyyyMMdd and MM/dd/yyyy.
In the code below, I created an array that you can add additional formats to parse.
Here is the modified code - I used "Microsoft" string to test:
# Add additional property InstallDateObj that will hold the parsed DateTime object
$Installed_Software=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate, InstallDateObj
$SoftwareInstall_Days = 40
$TheDate = (([datetime]::Now))
# Try to parse dates.
$Installed_Software.ForEach({
# add more formats if you need
[string[]] $formats = #("yyyyMMdd","MM/dd/yyyy")
$installDate = $_.InstallDate
$installedDateObj = $null;
$formats.ForEach({ [DateTime] $dt = New-Object DateTime; if([datetime]::TryParseExact($installDate, $_, [System.Globalization.CultureInfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None, [ref]$dt)) { $installedDateObj = $dt} });
$_.InstallDateObj = $installedDateObj
})
$Installed_recently= #()
$Installed_recently=($Installed_Software |
Where-Object {($_.DisplayName -match ('Microsoft') -or $_.Publisher -match ('Microsoft') -and ($_.InstallDateObj -ne $null) -and ($TheDate - $_.InstallDateObj).Days -le $SoftwareInstall_Days)})
if($Installed_recently.Count -gt 0) {
Write-Output "Relevant software was recently installed and/or repaired / modified.`nThese are the items:`n"
Write-Output $Installed_recently
}
else {
Write-Output "No relevant software was recently installed and/or repaired / modified."
}
There are methods for datetime object to add or remove minutes,hours,days,years from datetime, see script below.
For your current exmaple of getting software from registry, InstallDate need to be converted to a datetime format and then compared to a needed date from report.
#Report date 40 days
$SoftwareInstall_Days = (([datetime]::Now)).AddDays(-40)
#Getting installed software and filtering based on report date
$Installed_recently=Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName, DisplayVersion, Publisher, #{Name = "InstalledDate" ; Expression = {[DateTime]::ParseExact($_.InstallDate, 'yyyyMMdd', [Globalization.CultureInfo]::InvariantCulture) } }| where {$_.installeddate -ge $SoftwareInstall_Days}
#Output
$Installed_recently
Installed software also can be gathered from different places:
#Using WMI
Get-WmiObject -Class Win32_Product
#Using PS Software provider
Get-Package

Property has a value but cannot select it

I have a function that checks the registry for an uninstall key called Get-InstalledApps
Function Get-InstalledApps {
param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[string]$NameRegex = ''
)
foreach ($comp in $ComputerName) {
$keys = '','\Wow6432Node'
foreach ($key in $keys) {
try {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
$apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
} catch {
continue
}
foreach ($app in $apps) {
$program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
$name = $program.GetValue('DisplayName')
if ($name -and $name -match $NameRegex) {
[pscustomobject]#{
ComputerName = $comp
DisplayName = $name
DisplayVersion = $program.GetValue('DisplayVersion')
Publisher = $program.GetValue('Publisher')
InstallDate = $program.GetValue('InstallDate')
UninstallString = $program.GetValue('UninstallString')
Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
Path = $program.name
}
}
}
}
}
}
and then I grab the DisplayName/Version for what I need. My current problem is that it only seems to work on certain machines. Example:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"}
Name Value
---- -----
InstallDate
ComputerName Computer
DisplayName Citrix Receiver 4.7
Bits 64
UninstallString C:\ProgramData\Citrix\Citrix Receiver 4.7\TrolleyExpress.exe /uninstall /cleanup
Path HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\CitrixOnlinePluginPackWeb
Publisher Citrix Systems, Inc.
DisplayVersion 14.7.0.13011
So this is great, I get what I want. Now I normally just pipe in | Select-Object Displayname -ExpandProperty Displayname and it would return "Citrix Receiver 4.7" just like I want. My problem is that on certain machines I'm getting this:
Get-InstalledApps | Where-Object {$_.Displayname -like "*Citrix Receiver*"} | Select-Object DisplayName
DisplayName
-----------
And that's it. Why is there no value listed? If I try to expandproperty I get an error because it says nothing is there, but clearly there is something there or the Where-Object would not have found it in my search. Again, in a lot cases this code works just fine and I get the value I want but on a lot of machines I'm getting what you see above.
Edited in from comments:
I run this on a user's machine and I get the results I posted. If I run it on my machine I'll get the value "Citrix Receiver 4.7" every time. Also, on my machine I don't get the Name and Value columns. Only about 1/4 of the machines I ran this code on actually gave me the value I expected. Windows 7 vs Windows 10 thing?
It looks to me like your function returns a [hashtable], but you're using it like it's an object with properties.
That happens to work fine with Where-Object because the .Member syntax works for accessing [hashtable] values, but it's not going to work with Select-Object because it's operating on actual properties.
So what can you do?
If you want to keep it as a [hashtable], and insist on doing it in a pipeline, you can use ForEach-Object:
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -Process { $_.DisplayName }
or
Get-InstalledApps |
Where-Object {$_.Displayname -like "*Citrix Receiver*"} |
ForEach-Object -MemberName Item -ArgumentList DisplayName
Another thing you can do is change your function to return an object.
This is really easy to do with a [hashtable]; so say your function is about to return $hash, instead return:
New-Object -TypeName PSObject -Property $hash
Now you can use the normal suite of cmdlets and have them work as expected.
Edit: after seeing your code, it looks like you are converting your hashtable to an object already, but your output says otherwise. It wouldn't display as Name and Value columns if that were the case, so I still think something is wrong and the output is a [hashtable].
Edit 2: with info from comments about the platform differences, this seems to be happening because the object conversion is being done with the [pscustomobject] type accelerator which was added in PowerShell v3. Since the problematic machine is running Windows 7, it may be running v2 (which is what Win 7 shipped with).
Recommendations:
Get rid of Windows 7.
If you can't do that, upgrade PowerShell (Windows Management Framework) on that machine.
Either way, use New-Object as posted above.

Powershell - Bytes sent/received

I need to create a script that samples the network traffic every 30 seconds and stores the bytes sent/received. This data is then later used to draw graphs. I wrote one that works perfectly on Windows 2012 but i realised some of the cmdlets were not available in previous versions like 2008 so i am seeking alternatives.
For windows 2012 i used get-netadapterstatistics to get the received/sent bytes but this won't work on pre 2012 so i thought i could use netstat -e but the problem is both are giving me completely different results and i was hoping someone can tell me why? The script below was written to see the different between data.
function getNic{
$nic = Get-NetRoute | ? DestinationPrefix -eq '0.0.0.0/0' | Get-NetIPInterface | Where ConnectionState -eq "Connected" | Select -ExpandProperty InterfaceAlias
return $nic
}
function getBR{
$b = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
$a = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$a - $script:startbr
$b - $script:startbr2
$script:startbr = $a
$script:Startbr2 = $b
}
$nic_name = getNic
$startbr = (Get-NetAdapterStatistics |Where InterfaceAlias -eq $nic_name |Select -ExpandProperty SentBytes)
$startbr2 = ((netstat -e | Select-String "Bytes") -split '\s+')[2]
for(1..1000){
getBR
Start-Sleep 5
}
The results are as below
0
0
4577
18308
6695
26780
9055
36220
Ideally i am only interested in capturing traffic on the external interface.
While i can´t offer you an explanation for the difference between your methods i could offer you an alternative that should work on pre 2012 as well as on 2012 upwards:
$ifIndex = Get-WmiObject -Class win32_ip4routetable | where {$_.destination -eq "0.0.0.0"} | select -ExpandProperty InterfaceIndex
$ifIndex = "InterfaceIndex=" + $ifIndex
$nic_name = Get-WmiObject -Class win32_networkadapterconfiguration -Filter $ifIndex | select -ExpandProperty Description
$nic = [System.Net.NetworkInformation.Networkinterface]::GetAllNetworkInterfaces() | where {($_.description -eq $nic_name) -and ($_.operationalstatus -eq "up")}
$stats = $nic.GetIPv4Statistics()
$bytesSent = $stats.BytesSent
$bytesReceived = $stats.BytesReceived
This gives results consistent with the Get-NetAdapterStatistics Cmdlet on my system
After thinking about it maybe netstat shows statistics for multiple network adapters (maybe including loopback) combined since there is no differentiation by nic? Just guessing but this might explain the increased bytecount. Sadly there´s no details to be found in the docs

Powershell - how to replace OS Version number with String

I am querying remote servers for their operating system. I know that I can return the Version, but I want to replace these values with the friendly name. The code I have so far is:
$Computer = (gc c:\servers.txt)
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $Computer -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
If ({$BuildVersion.Version -match "5.2.3790"})
{$Build="2003"}
Elseif ({$BuildVersion.Version -match "6.1.7601"})
{$Build="2008"}
Elseif ({$BuildVersion.Version -like "6.3.9600"})
{$Build="2012"}
But this doesn't seem to work and only returns "2003" regardless. Please help, I'm fairly new to PS and coding.
thanks
The problem is your if statements. Putting the Boolean expression inside squiggly brackets makes it a script block, and that's going to get cast as a string before being cast as a Boolean. Strings cast to Booleans always evaluate to true unless they're empty.
PS C:\> {$BuildVersion.Version -match "5.2.3790"}
$BuildVersion.Version -match "5.2.3790"
PS C:\> ({$BuildVersion.Version -match "5.2.3790"}) -as [bool]
True
PS C:\> $BuildVersion.Version -match "5.2.3790"
False
PS C:\> ($BuildVersion.Version -match "5.2.3790") -as [bool]
False
So what you're running is essentially:
if ([bool]'$BuildVersion.Version -match "5.2.3790"') [...]
And that's always going to be true.
Try:
$Computer = (gc c:\servers.txt)
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $Computer -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
If ($BuildVersion.Version -match "5.2.3790")
{
$Build = "2003"
}
Elseif ($BuildVersion.Version -match "6.1.7601")
{
$Build = "2008"
}
Elseif ($BuildVersion.Version -like "6.3.9600")
{
$Build = "2012"
}
Bottom line is that squiggly brackets are not parentheses and you can't use them like they are.
However, there's also a major logic error here. You're potentially fetching an array for $BuildVersion because you're reading from a file, but then you treat it like a single value. You never loop through $BuildVersion. However, I do not have enough information about what you're actually trying to do with your script (like what you do with $Build) to be able to fix that.
I originally said this, but I've since changed my mind
The reason this is only returning 2003 is that you're only running your If code on a single entry in the list.
Wrong
As TessellatingHeckler says, the reason your if wasn't working is that you had too many curly braces, so PowerShell wasn't actually evaluating your logic.
However, you still need to step through each of the computers to do what you're trying to do. We'll do that by adding in a ForEach loop. I also went ahead and replaced your If {} logic with a Switch statement, which I think is easier to understand for a scenario like this with multiple clauses. If's just get way too verbose.
Finally, I'm assuming you want to output the results too, so I added a custom object here, which is just a way of choosing which properties we want to display.
$Computer = (gc c:\servers.txt)
ForEach ($system in $computer){
$BuildVersion = Get-WmiObject -Class Win32_OperatingSystem -Property Version, CSName -ComputerName $system -ErrorAction SilentlyContinue
$Build=$BuildVersion.version
switch ($build){
"5.2.3790" {$Build="2003"}
"6.1.7601" {$Build="2008"}
"6.3.9600" {$Build="2012"}
}
#output results
[pscustomobject]#{Server=$system;OSVersion=$build;CSName=$buildVersion.CSname}
}#EndOfForEach
Output
>Server OSVersion CSName
------ --------- ------
dc2012 2012 DC2012
sccm1511 2012 SCCM1511
You can use this:
Get-WmiObject -Class Win32_OperatingSystem | Select-Object -ExpandProperty Caption
Additionally you can see everything this WMI object holds like this:
Get-WmiObject -Class Win32_OperatingSystem | fl *
Edit: if you want to remove some text from the string, you can use -replace:
(Get-WmiObject -Class Win32_OperatingSystem |
Select-Object -ExpandProperty Caption) -replace "Microsoft Windows Server ",""

Getting a free drive letter

I saw the Get-NextFreeDrive function in this answer and I wondered if there was a more efficient way to do this. It appears that the function in the linked answer keeps going through all the letters even if it has already found a free drive letter.
At PowerShell Magazine, we ran a brain teaser contest to find out the shortest answer to your question. Check this:
http://www.powershellmagazine.com/2012/01/12/find-an-unused-drive-letter/
There are several answers but here is my fav one:
ls function:[d-z]: -n | ?{ !(test-path $_) } | random
My two cents:
get-wmiobject win32_logicaldisk | select -expand DeviceID -Last 1 |
% { [char]([int][char]$_[0] + 1) + $_[1] }
Range of valid [CHAR] is 68..90, adding a check if [char]$_[0] -gt 90 avoid unexpected results.
In case some unit is a mapped network drive it return always the major successive, ex.:
c: system drive
d: cd/dvd
r: network mapped drive
the command return s: and not e: as [string]
This give the first free drive letter ( a little ugly.. someone can do it better IMO):
$l = get-wmiobject win32_logicaldisk | select -expand DeviceID | % { $_[0] }
$s = [int][char]$l[0]
foreach ( $let in $l )
{
if ([int][char]$let -ne $s)
{
$ret = [char]$s +":"
break
}
$s+=1
}
$ret
I like this way, for the following reasons:
It doesn't require WMI, just regular powershell cmdlets
It is very clear and easy to read
It easily allows you to exclude specific driveletters
It easily allows you to order the driveletters in any order you would like
It finds the first non used driveletter and maps it, and then it is finished.
$share="\\Server\Share"
$drvlist=(Get-PSDrive -PSProvider filesystem).Name
Foreach ($drvletter in "DEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray()) {
If ($drvlist -notcontains $drvletter) {
$drv=New-PSDrive -PSProvider filesystem -Name $drvletter -Root $share
break
}
}
Here's what I came up with. I need the last available drive letter from A to Z.
$AllLetters = 65..90 | ForEach-Object {[char]$_ + ":"}
$UsedLetters = get-wmiobject win32_logicaldisk | select -expand deviceid
$FreeLetters = $AllLetters | Where-Object {$UsedLetters -notcontains $_}
$FreeLetters | select-object -last 1
This gets an array of letters A..Z
Then gets an array of the letters already in use from WMI
Next produces an array of letters not in use using the comparison operator -notcontains
Finally outputs a single letter.
$taken = Get-WmiObject Win32_LogicalDisk | Select -expand DeviceID
$letter = 65..90 | ForEach-Object{ [char]$_ + ":" }
(Compare-Object -ReferenceObject $letter -DifferenceObject $taken)[1].InputObject
Just for fun to shave an extra line of code (lol). If you wanted to be cloppy as heck you could skip instantiating variables and just pipe those directly into -Ref and -Diff directly, probably ought to be slapped for doing that though. :)
Selects [1] to avoid getting the A: drive just in case that might complicate matters.
I had to Write a function that works with Powershell V2.0.
The following Function will Return the next available letter, it also can get an exclude letter as parameter:
Function AvailableDriveLetter ()
{
param ([char]$ExcludedLetter)
$Letter = [int][char]'C'
$i = #()
#getting all the used Drive letters reported by the Operating System
$(Get-PSDrive -PSProvider filesystem) | %{$i += $_.name}
#Adding the excluded letter
$i+=$ExcludedLetter
while($i -contains $([char]$Letter)){$Letter++}
Return $([char]$Letter)
}
Let's say Your OS reports drive-letters C:,E:,F: and G: as being used.
Running: $First = AvailableDriveLetter ,Will result in $First containing 'D'
Running: $Sec = AvailableDriveLetter -ExcludedLetter $First ,Will result in $Sec containing 'H'
Another way...
$DriveList = Get-PSDrive -PSProvider filesystem | foreach({($_.Root).Replace(":\","")})
$AllDrives = [char[]]([int][char]'E'..[int][char]'Z')
$NextDriveLetter = ($AllDrives | Where-Object { $DriveList -notcontains $_ } | Select-Object -First 1) + ":"
I found to my own cost that the currently accepted answer (ls function:[d-z]: -n | ?{ !(test-path $_) } | random) can indeed return things like CD drives.
I've made this one to exclude any local drives from the array:
"$([char[]]([char]'D'..[char]'Z')|Where-Object {((Get-WmiObject -Class Win32_LogicalDisk).DeviceID).replace(':','') -notcontains $_ }|Select-Object -first 1):"
It will return the first available letter. If you'd prefer the last available letter just change Select-Object -first 1 to Select-Object -last 1
I found out that Test-Path evaluates my empty CD-Drive as False, here is another alternative that will compare every letter in the alphabeth until it finds one that doesn't exist in filesystem, then returns that drive as output.
$DriveLetter = [int][char]'C'
WHILE((Get-PSDrive -PSProvider filesystem).Name -contains [char]$DriveLetter){$DriveLetter++}
Write-Host "$([char]$Driveletter):"
just going to add one that works for remote drive letters
$computer will be the input and $driveletter would contain the next available drive on the remote computer
67..90 | foreach {if(((GWmi win32_logicaldisk -computer $computer -Property DeviceID).deviceID).Substring(0,1) -notcontains [char]$_){$driveLetter = [char]$_; break}}
might be able to shorten that but at that length its clear to see whats going on
This seems a bit like a "me too" answer, but I noticed that all the other answers use -contains or -notcontains and I just didn't like those solutions. So this may not be terribly efficient, but I like it better. The purpose of this code (for me) was to find the first drive that I can use to create a drive mapping.
$FreeDrive=Get-PSDrive -PSProvider FileSystem | Select-Object -ExpandProperty Name | Where-Object { ($_ -ne "A") -and ($_ -ne "B") -and ($_ -ne "C") } | ForEach-Object { [System.Convert]::ToByte([System.Convert]::ToChar($_)) }
$FreeDrive=#($FreeDrive)
if (($FreeDrive.Count -eq 1) -and ($FreeDrive[0] -ne "Z")) { $FreeDrive=[System.Convert]::ToChar($FreeDrive[0]+1) }
$j=0
while ((($FreeDrive[$j]+1) -eq $FreeDrive[$j+1]) -and ($j -lt ($FreeDrive.Count-1))) { $j++ }
$FreeDrive=[System.Convert]::ToChar($FreeDrive[$j]+1)
$FreeDrive