what is the [0] doing in this code? - powershell

I have taken over some code from someone that has left and am wondering what the [0] means in the below code that the previous person wrote?
I mean, he wrote this:
$os = (Get-WmiObject -computername $hostfqdn -class Win32_OperatingSystem -credential $credential)
$ostitle = #($os)[0].Caption+" SP"+#($os)[0].ServicePackMajorVersion+"."+#($os)[0].ServicePackMinorVersion
But, if I try the below I get the same result as if I add a [0] in?
PS C:\> $os = (Get-WmiObject -computername SERVER-class Win32_OperatingSystem)
PS C:\> #($os)[0].Caption
Microsoft Windows Server 2008 R2 Enterprise
With a [0]:
PS C:\> #($os).Caption
Microsoft Windows Server 2008 R2 Enterprise
The entire function is:
function getoperatingsystem([string]$hostfqdn, [object]$credential, [int]$serverid)
{
try {
$os = (Get-WmiObject -computername $hostfqdn -class Win32_OperatingSystem -credential $credential)
$ostitle = #($os)[0].Caption+" SP"+#($os)[0].ServicePackMajorVersion+"."+#($os)[0].ServicePackMinorVersion
UpdateRecord "UPDATE t_server SET os='$ostitle' WHERE serverid=$serverid"
} catch [Exception] {
$errmsg = $error[0]
$currentuser = [Environment]::UserName
$datetimestamp = get-date
writelog "$datetimestamp,$currentuser,[getoperatingsystem],$hostfqdn,$errmsg"
$error.clear()
return $false
}
}

#($os)[0] Means he is creating an array on the fly, with one element, and he is accessing the array's first element with [0] (its index in the array).
He should just have used $os

Related

Powershell lookup RAM information on remote computer and save Partnumbers in to diffent variable

I have a simple script that can pull RAM partnumbers from a remote computer and search google for it. But it does not work as intended. If there's only 1 RAM module installed in the remote computer, it works great google opens with the search result for the Partnumber, yay!.
if there are more than 1 RAM module installed in the remote computer, the first Partnumber in the variable gets searched for in Google. The 2'nd, 3'rd, 4'th partnumber gets typed in to Chrome tab 2,3,4 as an address.
How can I get Chrome to search for all Partnumbers via Google?
My script:
$ComputerName = Read-Host "Write Computer Name"
Get-WmiObject Win32_PhysicalMemory -computername $ComputerName
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {$Partnumber = Get-WmiObject Win32_PhysicalMemory -computername $ComputerName | select -expandproperty Partnumber
Start-Process "chrome.exe" ("https://www.google.com/search?q=$Partnumber")}
if ($ToChrome -eq 'n') {Continue}
That is because chrome.exe interprets the space between the part numbers as new addresses.
I took the liberty to pimp the script with try&catch,a logfile output and the computername as a parameter so that you can call it as Get-MemoryPropertyAndSearchWithGoogle.ps1 -ComputerName ComputerName1
For my testing I used the attribute DeviceLocator as my PartNumber was empty.
#Get-MemoryPropertyAndSearchWithGoogle.ps1
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[string]$ComputerName
)
$ErrorPreference='Stop'
$ErrorActionPreference='Stop'
$LogFilePath = "C:\Temp\$((Get-Date).ToString("yyyy-MM-dd"))$($ComputerName)Get-MemoryPropertyAndSearchWithGoogle.log"
[string]$LogFileString = ""
#$Property = "PartNumber"
$Property = "DeviceLocator"
$ErrorExists = $false
$ComputerMemoryObjects = #()
try
{
$ComputerMemoryObjects = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName -Property *
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#Get-WmiObject Win32_PhysicalMemory -ComputerName $($ComputerName)`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
[string]$SearchString = ""
foreach ($SingleComputerMemoryObject in $ComputerMemoryObjects)
{
if ($SearchString)
{
$SearchString += "+OR+"
}
$SearchString += "$($SingleComputerMemoryObject.$Property)"
}
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y')
{
if ($SearchString)
{
try
{
Start-Process "chrome.exe" ("https://www.google.com/search?q=$($SearchString)")
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#chrome.exe started with searchstring:`"$($SearchString)`"`n"
}
catch
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#ERR#$($error[0].exception.message)`n"
$ErrorExists = $true
}
}
else
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#`$SearchString is empty`n"
}
}
if (!($ErrorExists))
{
$LogFileString += "$((Get-Date).ToString("yyyy-MM-dd_HH:mm:ss"))#INF#ScriptCompletedWithoutErrors`n"
}
$LogFileString | Out-File $LogFilePath
$LogFileString
You get multiple objects from Get-WmiObject. You need a loop if you want to do something for each of them.
Also, URL-encoding things that you put into a URL is a good idea. and maybe putting it in double-quotes won't hurt.
Add-Type -AssemblyName System.Web # for [System.Web.HttpUtility]::UrlEncode()
$ComputerName = Read-Host "Write Computer Name"
$installed_memory = Get-WmiObject Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object Manufacturer,PartNumber,SerialNumber,DeviceLocator,Capacity
$installed_memory | Format-Table -AutoSize
$ToChrome = Read-Host 'Do you want to search Google for the Partnumber(s)? Y Or N'
if ($ToChrome -eq 'Y') {
$unique_numbers = $installed_memory.Partnumber.Trim() | Sort-Object -Unique
foreach ($number in $unique_numbers) {
$query = [System.Web.HttpUtility]::UrlEncode('"' + $number + '"')
Start-Process chrome.exe "https://www.google.com/search?q=$query"
}
}
Powershell has a handy convenience feature: When you have an array of objects, you can query nested properties from all of them in one go.
For example, if there are 4 Win32_PhysicalMemory objects in $installed_memory, then
$installed_memory.Partnumber.Trim()
gives you 4 readily trimmed part numbers in a single step.

Loop through servers and output results along with errors

I wrote a simple PowerShell script to retrieve a list of servers' last boot time and output the results to grid view. The results are immediately shown in the grid window but and comes to a short pause whenever a server is not responding to the get command, either due to WMI not running or class not registered. It then displays the error in PS and move to the next server.
Now, the results aren't helpful unless the "not responding" servers are shown in the results windows.
$servers = ('serverx','serverb')
Get-WmiObject -Class Win32_OperatingSystem -ComputerName $servers |
select csname, #{LABEL='LastBootUpTime';EXPRESSION={$_.ConvertToDateTime($_.LastBootupTime)}},
#{LABEL='LocalTime';EXPRESSION={$_.ConvertToDateTime($_.LocalDateTime)}},
#{LABEL='UpTime';EXPRESSION={(Get-Date) - $_.ConvertToDateTime($_.LastBootupTime)}},
#{LABEL='OS';EXPRESSION={$_.Caption}} |
Out-GridView
Errors type shown in PS window in Red:
Get-WmiObject : Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)) At line:1 char:12
Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA) At line:1 char:12
Edit: How do I can i output the good results along with the server name if the servers that responded with an error?
For your desired result you need to query the servers individually and construct a custom object if the query fails:
$svr = 'serverx'
try {
Get-WmiObject Win32_OperatingSystem -Computer $svr -EA Stop |
select csname, #{n='LocalTime';e={...}},
#{n='UpTime';e={...}}, #{n='OS';e={...}}
} catch {
New-Object -Type PSObject -Property #{
csname = $svr
LocalTime = $null
UpTime = $null
OS = $null
}
}
Run this in a loop
$servers | ForEach-Object {
...
} | Out-GridView
Use background jobs (or something similar) instead of a plain loop to speed up the checks by running them in parallel rather than sequentially. Spawn each check as a job in the background and check for completed jobs in a loop until all jobs have completed. Collect the output from completed jobs.
Here is the full script that loops through the servers, catches non-terminating error and output to a window.
$svr = ('localhost','fail')
$Output = Foreach ($server in $svr)
{
try {
Get-WmiObject Win32_OperatingSystem -ComputerName $server -EA STOP |
select csname, #{n='LocalTime';e={$_.ConverttoDateTime($_.lastbootuptime)}},
#{n='UpTime';e={....}}, #{n='OS';e={"...."}}
} catch {
New-Object -Type PSObject -Property #{
Csname = $server
LocalTime = $null
UpTime = $null
OS = "Error" #$null
}
}
}
$output | Out-GridView

Issues automating printer driver update (printer settings) and printer preferences in Win7, using a PS,cmd,vbs,etc script?

WMI can do it, but I have an issue, PCs are on, but logged off. If I try to run:
wmic /node:%strIP% printer where DeviceID="lp1" set DriverName="Lexmark Universal v2"
It fails with a message about a "generic failure". I RDP in and then run the same command from my end, and it works. Powershell version I am using is older, so it does not have some of the printer cmdlets, and updating PS is currently out of the question. Is there a way to remotely log someone in, without actually having to RDP in? Via PS, cmd, PSEXEC, etc?
The other avenue I've taken is using regedit, but I'm hitting some hicups with that, namely that I cannot figure out what to copy. In regedit, I can change the drivername and the setting that enable duplex and tray2 (in printer settings), but I cannot figure how to change the settings in printer preferences for printing double sided and doing so along the long edge.
What I did to figure out what to change, I did a find on the printer name in regedit as a data value and exported the keys before changing the settings. Then I exported it again AFTER changing the settings. I then used fc /c /a /u before.reg after.reg to get the changes. I chopped up the .reg to include only the changed values. Running the .reg seems to change everything, but the print both sides, along the long edge settings. It is a lexmark printer, so I am wondering if maybe preferences for it are stored elsewhere.
This is my most up to date PS1 script. I've commented out some lines as I tried different ways of doing things:
$Cred = Get-Credential
$Str = Read-Host "Please select a site ID [###] "
$PC = Read-Host "Please select a PC number [##] "
Clear-Host
$PCNm = "$Str-CCPC-$PC"
function Test-PsRemoting
{
try
{
$errorActionPreference = "Stop"
$result = Invoke-Command -ComputerName $PCNm { 1 }
}
catch
{
Write-Verbose $_
return $false
}
if($result -ne 1)
{
Write-Verbose "Remoting to $PCNm returned an unexpected result."
return $false
}
$true
}
If(!(Test-PsRemoting)){
PSEXEC \\$PCNm powershell Enable-PSRemoting -force 2>&1 >nul
Clear-Host
Write-Host "Enabled PsRemoting"
}else{Write-Host "PsRemoting already enabled"}
Invoke-Command -ComputerName $PCNm -Credential $Cred -ScriptBlock {
#$lp1 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
$lp1 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp1"}
$lp1.Scope.Options.EnablePrivileges = $true
$lp1.DriverName = "Lexmark Universal v2"
$lp1R = $lp1.Put()
#$lp2 = Get-WMIObject -Query "SELECT * from Win32_Printer Where DeviceID='lp2'"
$lp2 = Get-WmiObject Win32_Printer | ?{$_.name -eq "lp2"}
$lp2.Scope.Options.EnablePrivileges = $true
$lp2.DriverName = "Lexmark Universal v2"
$lp2R = $lp2.Put()
}
#$lp1 = Get-WMIObject -Impersonation Delegate -Authentication Call -Credential $Cred -ComputerName $PCNm -Query "SELECT * from Win32_Printer Where DeviceID='lp1'"
#$lp1.DriverName = "Lexmark Universal v2"
#$lp1.Put()
No matter which way I try it, invoke-command, or get-wmiobject, I get:
Exception calling "Put" with "0" argument(s): "Generic failure "
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
+ PSComputerName : 150-CCPC-02
This doesn't particularly answer your actual question but as a solution for how I do this very same thing I thought I would give you what I threw together to update printer properties. I have not cleaned this up at all as I was porting it from my create printer function.
Function Set-SSPrinter {
Param(
[Parameter(Mandatory=$true,
ValueFromPipeline=$True,
ValueFromPipelineByPropertyName=$True)]
[ValidateNotNullOrEmpty()]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$Name,
[string]$PortName,
[string]$DriverName,
[string]$Comment,
[string]$Location,
[bool]$Shared,
[string]$ShareName = $Name,
[string]$PermissionSDDL,
[string]$PrintProcessor,
[string]$DataType,
[bool]$RawOnly
)
try {
$modprinter = Get-WmiObject Win32_Printer -ComputerName $ComputerName | ?{$_.name -eq $Name}
$modprinter.Scope.Options.EnablePrivileges = $true
if($DriverName) {
$modprinter.DriverName = $DriverName
}
if($PortName) {
$modprinter.PortName = $PortName
}
if($Shared) {
$modprinter.Shared = $Shared
}
if($ShareName) {
$modprinter.ShareName = $ShareName
}
if($Location) {
$modprinter.Location = $Location
}
if($Comment) {
$modprinter.Comment = $Comment
}
if($Name) {
$modprinter.DeviceID = $Name
}
if($PrintProcessor) {
$modprinter.PrintProcessor = $PrintProcessor
}
if($DataType) {
$modprinter.PrintJobDataType = $DataType
}
if($RawOnly) {
$modprinter.RawOnly = $RawOnly
}
$result = $modprinter.Put()
if($PermissionSDDL) {
$modprinter.SetSecurityDescriptor($objHelper.SDDLToWin32SD($PermissionSDDL).Descriptor) | Out-Null
}
$("Update Complete: " + $Name)
} catch {
$("Update Failed: " + $Name)
Write-Warning $_.Exception.Message
$error.Clear()
}
}
Unfortunately I use the printer name to figure out which device to modify on the remote machine. Your executing credentials from the powershell session you have open must have admin rights on the remote machine. if necessary do a runas different user on powershell.exe
Example usage:
Set-SSPrinter -ComputerName "10.210.20.100" -Name "TestPrinter" -DriverName "Lexmark Universal v2"
wmic /node:servername /user:username /password:password path win32_something call methodname
Is how to do it.
Things with users are best done with logon scripts because that is how windows is designed.

Powershell remote static IP via script

I'm looking to write a Powershell function to assign a static IP address remotely to a computer specified at runtime. I've done some research and found the Win32_NetworkAdapterConfiguration class, which seems like exactly what I need. So I sat down and wrote myself a function:
Function Set-StaticIPAddress
{
param
(
[Parameter(Mandatory = $true,
Position = 0,
ValueFromPipeline = $true,
ValueFromPipelineByPropertyName = $true)]
[String] $ComputerName
,
[Parameter(Mandatory = $true,
Position = 1)]
[Alias("IPv4Address")]
[String] $IPAddress
,
[Parameter(Position = 2)]
[String] $SubnetMask = "none"
,
[Parameter(Position = 3)]
[String] $DefaultGateway = "none"
,
[Parameter(Position = 4)]
[String[]] $DNSServers = ("172.16.1.36","172.16.1.78")
,
[Parameter(Position = 5)]
[PSCredential] $Credential
)
process
{
# There's some error-checking here that I've snipped out for convenience
Write-Verbose "Testing connection to $ComputerName"
if (-not (Test-Connection $ComputerName))
{
Write-Error "Unable to connect to $ComputerName."
return
}
Write-Verbose "Obtaining remote WMI reference"
if ($Credential)
{
$wmi = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $ComputerName -Filter "IPEnabled = 'True'" -Credential $Credential
} else {
$wmi = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $ComputerName -Filter "IPEnabled = 'True'"
}
Write-Verbose "Attempting to set DNS servers"
$wmi.SetDNSServerSearchOrder($DNSServers)
Write-Verbose "Attempting to set dynamic DNS registration"
$wmi.SetDynamicDNSRegistration($true)
Write-Verbose "Attempting to set static IP address and subnet mask"
$wmi.EnableStatic($IPAddress, $SubnetMask)
Clear-DnsClientCache #This may not be necessary; I added it as a troubleshooting step
Write-Verbose "Attempting to set default gateway"
$wmi.SetGateways($DefaultGateway, 1)
Write-Output $wmi
}
}
The trouble is, the EnableStatic method never seems to return a value - either a success or failure code. I tested this function on a machine sitting right next to me, and while the script was still waiting at the "Attempting to set static IP address and subnet mask" stage, I pulled up the configuration on the machine and found a static IP and subnet mask set. There was no default gateway (which makes sense, since I didn't get that far in the script). The computer in question didn't have network access, because the default gateway was missing.
I have also tried running the same commands from an interactive shell, and the same "freeze" happens on the same command:
$wmi.EnableStatic($IPAddress, $SubnetMask)
My best guess is that changing the network adapter configuration is breaking the remote WMI connection. Is there a way to make this work so I can script the assignment of a static IP address 100% remotely?
Edit: I MacGyvered another attempt which creates a ScriptBlock object and sends it to the remote computer using Invoke-Command. I had to do some interesting footwork to get an array of IP addresses to turn into a String literal, including the quotes, but I can now confirm that the script block has correct syntax and all that. Unfortunately, doing it this way causes my PS window to complain that the network connection has been lost (since the IP address has changed) and the script block does not complete successfully.
$wmiLiteral = '$wmi' # used so the script block actually creates and references a variable, $wmi
$script =
"$wmiLiteral = Get-WmiObject Win32_NetworkAdapterConfiguration -Filter ""IPEnabled = 'True'"";
$wmiLiteral.EnableStatic(""$IPAddress"", ""$SubnetMask"");
$wmiLiteral.SetGateways(""$DefaultGateway"", 1);
$wmiLiteral.SetDNSServerSearchOrder($DNSServersList);
Write-Output $wmiLiteral"
Write-Verbose "Script block:`n-------------`n$script`n---------------"
$scriptBlock = [scriptblock]::Create($script)
Write-Verbose "Testing connection to $ComputerName"
if (-not (Test-Connection $ComputerName))
{
Write-Error "Unable to connect to $ComputerName."
return
}
Write-Verbose "Invoking scriptblock"
if ($Credential)
{
$output = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock -Credential $Credential
} else {
$output = Invoke-Command -ComputerName $ComputerName -ScriptBlock $scriptBlock
}
You might have more luck using Invoke-Command to run netsh on the remote computer, which sets the IP address, subnet mask, and gateway in one command. However, netsh uses a different name for the interface, which you can get from the NetConnectionID property of the Win32_NetworkAdapter class.
$InterfaceName = $Get-WmiObject Win32_NetworkAdapter | ?{$_.Description -eq $wmi.Description} | select -ExpandProperty NetConnectionID
Invoke-Command -ComputerName $ComputerName -Credential $Credential -ScriptBlock {netsh interface ip set address $InterfaceName static $IPAddress $SubnetMask $DefaultGateway 1}
You can wrap the second line in another if ($Credential) block.
However, I strongly recommend that you verify that the filter is returning one object rather than an array of objects, as suggested in my comments above. Or, to be safe, change
$wmi = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $ComputerName -Filter "IPEnabled = 'True'" -Credential $Credential
to
$wmi = Get-WmiObject Win32_NetworkAdapterConfiguration -ComputerName $ComputerName -Filter "IPEnabled = 'True'" -Credential $Credential | select -First 1
That will ensure you're getting only one object, but of course it can't ensure that it's necessarily the one you want if there's more than one with IPEnabled true.
I would call your function in a script that use localHost as $computername parameter and then remote execute (invoke-command)this script on the remote computer, using powershell remote sessions (New-PSSession), or creating a remote PowerShell process with this script as parameter with WMI (in this case you have to copy the srcript on the remote compter).
You can also schedule this script on the remote computer ... the general idea is to not use the network during the operation.

Determine Users Accessing a Shared Folder Using PowerShell

I need to determine the users/sessions accessing a shared folder on a Windows XP (SP2) machine using a PowerShell script (v 1.0). This is the information displayed using Computer Management | System Tools | Shared Folders | Sessions. Can anyone give me pointers on how to go about this?
I'm guessing it will require a WMI query, but my initial search online didn't reveal what the query details will be.
Thanks, MagicAndi
I came up with the following script:
$computer = "LocalHost"
$namespace = "root\CIMV2"
$userSessions = Get-WmiObject -class Win32_ServerConnection -computername $computer -namespace $namespace
if($userSessions -ne $null)
{
Write-Host "The following users are connected to your PC: "
foreach ($userSession in $userSessions)
{
$userDetails = [string]::Format("User {0} from machine {1} on share: {2}", $userSession.UserName, $userSession.ComputerName, $userSession.ShareName)
Write-Host $userDetails
}
Read-Host
}
The following articles were useful:
http://www.activexperts.com/activmonitor/windowsmanagement/adminscripts/filesfolders/sharedfolders/
http://www.computerperformance.co.uk/powershell/powershell_wmi_shares.htm
http://www.codeproject.com/KB/cs/NetWorkSpy.aspx?msg=2384830
As always, if you can't find a way to do it in PowerShell, see if someone has done something similar in C#.
I've modified it a bit to show hostname instead of IP:
$computer = "LocalHost"
$namespace = "root\CIMV2"
$userSessions = Get-WmiObject -class Win32_ServerConnection -computername $computer -namespace $namespace
if($userSessions -ne $null)
{
Write-Host "The following users are connected to your PC: "
foreach ($userSession in $userSessions)
{
$ComputerName = [system.net.dns]::resolve($usersession.computername).hostname
$userDetails = [string]::Format("User {0} from machine {1} on share: {2}", $userSession.UserName, $ComputerName, $userSession.ShareName)
Write-Host $userDetails
}
Read-Host
}