Powershell function failure - powershell

I swear I'm missing something easy here...
Here's a simple script to get disk info:
function get-disks {
try { $disks = gwmi win32_logicaldisk -co $server}
catch { write "$server : Can't connect"}
}
get-disks
$disk.deviceid
The gwmi command alone works perfectly. The "$disks = gwmi..." command alone works perfectly. The try {...}catch{...} lines run alone work perfectly.
But as soon as I load the function and call 'get-disks' I receive no errors, but $disks is empty.

The $server parameter, and the $disks variable are local to the inside function and not visible (not defined) outside of the function.
You need to provide the server name as a function parameter (from the outside in), and you need to return the $disks variable value from the function (from the inside out) and capture it's value.
function Get-Disks {
param(
[Parameter(Mandatory = $true)]
[string] $Server
)
try {
$result = gwmi win32_logicaldisk -co $Server;
return $result # <--
}
catch { write "$Server : Can't connect"}
}
$disks = Get-Disks -Server "localhost"
Note that the $result variable inside the function is another
variable the the $disks variable outside of the function.
For simplicity, you can write the function as follows:
function Get-Disks {
param(
[Parameter(Mandatory = $true)]
[string] $Server
)
# just return the output of gwmi directly
gwmi win32_logicaldisk -co $Server;
}

Related

Powershell combine parameters inside a script with parameters entered on the command line?

Quick one? - As I'm still learning Powershell. I was wondering if it was possible to combine parameters inside a script with parameters entered on the command line?
i.e. I have a function like this as an example...
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
$SiteName = "London1"
$Subnet= "192.168.10.1/24"
$Cred = <supplied>
#$ComputerName = "blah1"
GetInfo $SiteName $Subnet $Cred $ComputerName
$SiteName = "London2"
$Subnet= "192.168.11.1/24"
$Cred = <supplied>
#$ComputerName = "blah2"
GetInfo $SiteName $Subnet $Cred $ComputerName
Now say inside the script I would specify the SiteName, Subnet, Cred.... but on the command line I would like to specify -ComputerName
But as I'm using the script & let's say I know that Lon1-PC1 is in "London1" I would like to do this on the calling command:
.\GetPCInf.ps1 -ComputerName "Lon1-PC1"
or
.\GetPCInf.ps1 -ComputerName "Lon2-PC1"
Obviously there will be additional logic inside the script to say that if the -ComputerName prefix is "Lon1" then do X or if -ComputerName prefix is "Lon2" then do Y..
Obviously I know I can just put the computername in the script, save it & run it.
So far when I try, nothing happens in relation to the -Computername return..
I haven't yet tried combining a parameter & Args - but I've read Args is not the best to use so I'm trying to avoid it.
If this can't be done then fair enough, just wondered if someone might know if this can be done, as it saves me typing in:
.\GetPCInf.ps1 -SiteName London1 -Subnet "192.168.10.1/24" -Cred mycreds -ComputerName "Lon1-PC1"
each time I want to run it....
I suppose I could create batch files to call the script & put %1 for computername in the batch file & call it that way, but just curious really..
Many thanks.
So far when I try, nothing happens in relation to the -Computername return..
Scripts are just functions stored in files - and they support param blocks and parameter declarations just like functions - so declare a $ComputerName parameter:
# GetPCInf.ps1
param(
[Parameter(Mandatory = $true)]
[string]$ComputerName
)
# define GetInfo function
function GetInfo {
param ($SiteName, $Subnet, $Cred, $ComputerName)
Write-Host "Checking $Site and $ComputerName"
<# ... Additional logic to check computername prefix & subnet etc. #>
}
# define table of arguments to pass to GetInfo
$GetInfoArgs = #{
ComputerName = $ComputerName
}
# add additional arguments based on computer name value
if($ComputerName -like 'Lon1-*'){
$GetInfoArgs += #{
SiteName = "London1"
Subnet= "192.168.10.1/24"
Cred = $(<# credential goes here #>)
}
} elseif($ComputerName -like 'Lon2-*') {
$GetInfoArgs += #{
SiteName = "London2"
Subnet= "192.168.11.1/24"
Cred = $(<# credential goes here #>)
}
} else {
$GetInfoArgs += #{
SiteName = "DefaultSite"
Subnet= "192.168.12.1/24"
Cred = $(<# credential goes here #>)
}
}
# invoke GetInfo function
GetInfo #GetInfoArgs

Simple powershell success or fail log

I'm looking for the easiest way to write the success or failure of a scheduled script to a csv file. I am not looking for a way to log the error or reason of failure just a simple "Succes/Failure". For this reason i'm not trying to fix/log non-terminating errors.
I thought the easiest way to put every script in a
try {
Write-Host "test"
}
catch{
Code to write to csv here
}
Block where I write to a csv file in the Catch part. Is there a better/easier way to do this or is this the right way to go?
Continuing from my comment. . .
Honestly, this really depends on the situation, i.e. what is you're trying to accomplish. So, let's make up a scenario of querying a computer for some basic info:
Function Get-SystemInfo {
Param (
[Parameter(Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME
)
Begin {
$ErrorActionPreference = 'SilentlyContinue'
}
Process {
foreach ($Computer in $ComputerName)
{
try {
# attempt cim session with -EA Stop to not allow it to go
# any further and we can calculate the results.
$CIMSession = New-CimSession -ComputerName $Computer -ErrorAction Stop
$Status = $true
# Perform the actual queries
$CS = Get-CimInstance -ClassName Win32_COmputerSystem -CimSession $CIMSession
$BS = Get-CimInstance -ClassName Win32_BIOS -CimSession $CIMSession
# evaluate against the returned objects
$UserName = if ($CS.UserName -eq $null) { 'No User Logged in' } else { $CS.UserName }
$Manufacturer = if ($CS.Manufacturer -eq $null) { 'N/a' } else { $CS.Manufacturer }
$Model = if ($CS.Model -eq $null) { 'N/a' } else { $CS.Model }
$SerialNumber = if ($BS.SerialNumber -eq $null) { 'N/a' } else { $BS.SerialNumber }
}
catch {
# Set the variables to $null
$Status = $false
$UserName = $null
$Manufacturer = $null
$Model = $null
$SerialNumber = $null
}
finally {
# Output the filled variables
[PSCustomObject] #{
ComputerName = $Computer
Connected = $Status
UserLoggedIn = $UserName
Manufacturer = $Manufacturer
Model = $Model
SerialNumber = $SerialNumber
}
}
}
}
End {
# cleanup
# some people argue this should be in the finally block
# to disconnect any machine, but looking at this from both
# sides, they both have pros/cons.
Get-CimSession | Remove-CimSession
}
}
... the biggest take-away from this quick function, is the -ErrorAction Stop while trying to create a CIM session. This is where looking at the bigger picture comes into play. If you are unable to connect to the computer, why bother continuing? This includes getting an echo reply from a quick ping since that doesn't dictate that you can connect to the remote PC just because you got a reply.
The rest is the if, and else statements that handle the light work evaluating against the returned objects for more control over the output.
Results would be:
PS C:\Users\Abraham> Get-SystemInfo
ComputerName : OER
Connected : True
UserLoggedIn : Abraham
Manufacturer : LENOVO
Model : 22251
SerialNumber : 55555555
PS C:\Users\Abraham> Get-SystemInfo -ComputerName BogusComputerName
ComputerName : BogusComputerName
Connected : False
UserLoggedIn :
Manufacturer :
Model :
SerialNumber :

Powershell to start/stop with arg

I've written the following function in test.ps1 and I would like to make a choise when running thsi script to start/stop/.. :
function getState($SeviceName) {
$server = #('host_1', 'host_2')
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
I would like to provide $ServiceName as argument (with stdin) how can I do it? => somthing like choose 1 to start 2 to stop ...
To use switch/case in Powershell
$doAction = {"Stop-Service", "Start-service"}
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName | $doAction}
How do I use the switch to select start or stop?
Here's a function that will do what you're asking for:
function Get-State {
[CmdletBinding()]
[OutputType('System.ServiceProcess.ServiceController')]
param(
[Parameter(Position = 0, Mandatory)]
[ValidateSet('Start', 'Stop', 'Get')]
[string] $Action,
[Parameter(Position = 1, ValueFromPipeline, Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $ServiceName
)
begin {
$serverList = #('host_1', 'host_2')
}
process {
foreach ($server in $serverList) {
try {
$svc = Get-Service -ComputerName $server -Name $ServiceName -ErrorAction Stop
} catch {
throw "Failed to find service $ServiceName on $server! $PSItem"
}
switch ($Action) {
'Start' { $svc | Start-Service -PassThru }
'Stop' { $svc | Stop-Service -Force -PassThru }
default { $svc }
}
}
}
}
It utilizes advanced function features and attributes to take pipeline input (stdin in your words). I'd suggest reading this documentation.
You can add argument to a script by adding parameters to it.
On the top of your script file put:
Param
(
[parameter()]
[String[]]$YourArgumentVariable
[parameter()]
[switch] $MySwitch
)
With a function it goes right after the function definition. So in your case:
function getState($SeviceName) {
Param
(
[parameter()]
[String[]]$server
[parameter()]
[switch] $MySwitch
)
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
A switch basically sets a boolean to true or false.
So in this if you call the script with -MySwitch it will set the variable $MySwitch to true. Else it will remain false.
Don Jones has written a good getting started article on paramters that I would recommend you checking out.
Do note that there are loads of things you can define in the paramter. Like if you want to make sure it is always filled you can set
[parameter(Mandatory=$true)]
This is just one of many examples of what you can do with paramters.

How can I use splatting in a parameterized function?

I'm writing a parameterized function and trying to use splatting to reuse the parameter-set.
$Computername='localhost'
$params = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #params
}
On executing like this it fails doesn't recognize the user-inputed servername. It still takes the value of $Computername initially specified.
Get-Diskinfo -drive c: Server1 administrator
How can I get it to identify the user-inputed computername? Am I missing something ?
Instead of splatting random hashtable, you need to splat something that is defined automatically by PowerShell:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
# Need to remove any parameter that is used "internally" and don't exist on cmdlet
$PSBoundParameters.Remove('drive') | Out-Null
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #PSBoundParameters
}
Get-Diskinfo -drive c: Server1 administrator
Alternatively: use passed parameters to build $parms inside your function:
function Get-Diskinfo {
Param ($drive,$computername,$credential)
$parms = #{
computername = $Computername
credential = $credential
}
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" #parms
}
You aren't calling #parms until inside your function.
This should work, using the parameters within your function to pull the WMI query:
$Computername='localhost'
$parms = #{'computername'=$Computername;
'credential'='administrator'}
function Get-Diskinfo {
Param ($drive,$computername,$credential)
Get-WmiObject win32_logicaldisk -Filter "DeviceID = '$drive'" -ComputerName $computername -Credential $credential
}
And then use the splatting to run the function:
Get-Diskinfo -drive c: #parm
The function can still be run using the parameters directly:
Get-Diskinfo -drive c: Server1 administrator
Your $params hashtable gets it's value when you create it, not when it's used. Your $computername,$credential parameters are never referenced inside your function, which means that they are dead parameters.
You can't use splatting and normal function-calling with parameters for the same parameter at the same time. You should only use parameters inside your function scriptblock. Splatting is for the end-user only. #Tim Ferril has shown you how to write the function properly.
If you need to be able to use both default values AND be able to change ex. ComputerName for one cmdlet, then you should use default values and not splatting. Ex:
function Test-Global {
param(
$ComputerName = $global:PC,
$Username = $global:username
)
"Computername is '$ComputerName'"
"Username is '$UserName'"
}
#Set the reusable variables in global scope and call your function
$global:Username = "Frode"
$global:PC = "MyPC"
PS C:\Users\Frode> Test-Global
Computername is 'MyPC'
Username is 'Frode'
#If I need to modify the server for one function, I'd simply specify that parameter
PS C:\Users\Frode> Test-Global -ComputerName server1
Computername is 'server1'
Username is 'Frode'
To make it easier for user to set the default values, you could create a function for that too.
#Create your reusable parameters
function Set-Params {
param(
$ComputerName,
$Username
)
if($Username) { $global:Username = $Username }
if($ComputerName) { $global:PC = $ComputerName }
}
PS C:\Users\Frode> Set-Params -ComputerName Test1 -Username Test1
PS C:\Users\Frode> Test-Global
Computername is 'Test1'
Username is 'Test1'
PS C:\Users\Frode> Set-Params -ComputerName Test2
PS C:\Users\Frode> Test-Global
Computername is 'Test2'
Username is 'Test1'

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.