Viewing entries in
PowerShell

PowerShell: Check for NetApp failed and unowned disks (CDOT)

PowerShell: Check for NetApp failed and unowned disks (CDOT)

Why I needed this

I received a call from a technician in another site one day to let me know that we had 3 amber lights in our NetApp cluster. While autosupport should have caught this, it didn't (one controller showed them pre-failed, and the other showed them as failed disks).

So I turned to my love of PowerShell and added to the NetApp functions I wrote that perform actions when connected to NetApp controllers. If you haven't seen those posts, check them out!

How to use this

You'll need to first be connected to a NetApp controller and have the NetApp PowerShell module installed. Once that part is completed, you can use the code below. I also like to set a global variable for $foregroundColor, and this script utilizes that.

The Code

$netAppSystemInfo = Get-NCNode

        Write-Host `n"Controller information (CDOT)"`n -ForegroundColor $foregroundColor

        foreach ($node in $netappSystemInfo) {
            
            Write-Host "Node" $node.Node ` -foregroundcolor $foregroundcolor
            Write-Host "Model   :" $node.NodeModel
            Write-Host "Serial #:" $node.NodeSerialNumber
            Write-Host "Location:" $node.NodeLocation
            Write-Host "Healthy?:" $node.IsNodeHealthy
            Write-Host "Uptime  :" $node.NodeUptime
            Write-Host "ONTAPver:" $node.ProductVersion `n
            
        }
$failedDisks = Get-NcDisk | ?{ $_.DiskRaidInfo.ContainerType -eq "broken" }
        
        if ($failedDisks) {
        
            Write-Host `n"There are failed disks on this NetApp cluster!" -foregroundColor Red
            Write-Host "Failed Disks:"`n -foregroundColor $foregroundcolor
            
            foreach ($disk in $failedDisks){
                
                Write-Host "Disk Name      :" $disk.DiskName
                Write-Host "Disk UID       :" $disk.DiskUID
                Write-Host "Disk Controller:" $disk.NcController
                Write-Host "Disk Bay       :" $disk.Bay
                Write-Host "Disk Shelf     :" $disk.Shelf
                Write-Host "Disk model/FW  :" $disk.Model "/" $disk.FW `n
                
                
            }
            
            $openCase = Read-Host "Would you like to open a support case?"
            if ($openCase -like "*y*") {
                
                Start-Process "http://mysupport.netapp.com/cssportal/faces/oracle/webcenter/portalapp/pages/css/casesparts/CreateCaseLanding.jspx?_adf.no-new-window-redirect=true"
                
            }
            
        }
        
        
        $unOwnedDisks =  get-NCDisk
        
        foreach ($d in $unOwnedDisks) { 

            if ($d.diskownershipinfo.OwnerNodeName -eq $null) {
                
                Write-Host `n"Unowned disks found!" -foregroundColor Red
                Write-Host "Unowned Disks:"`n -foregroundcolor $foregroundColor
                Write-Host "Disk Name:" $d.DiskName
                
                $setOwner = Read-Host "Set owner to one of the nodes in the cluster?"
                
                if ($setOwner -like "*y*") {
                    
                    $i = 0 
                    foreach ($node in $netAppSystemInfo) {
                        
                        Write-Host $i -> "Node:" $node.Node
                        $i++
                    
                }
                
                $node = Read-Host "Which node #?"
                $nodeSystemID = $netAppSystemInfo[$node].NodeSystemID
                $nodeName = $netAppSystemInfo[$node].node
                $diskname = $d.DiskName
                $confirm = Read-Host "Set disk owner for disk $diskName to:" $nodeName "with systemID" $nodeSystemID"?"
                
                if ($confirm -like "*y*") {
                    
                    Set-NCDiskOwner -DiskName $diskName -NodeId $nodeSystemID
                    
                }
                    
                    
                    
                }

            } 
        
    
        }

What it will do

It will hunt down any failed or unowned disks. It will then prompt you to open the support page to open a new case. If it finds any unowned disks, it will give you the option to assign them to the controller of your choice.

As always, let me know if you have any feedback!

-Ginger Ninja

 

PowerShell: Working with CSV Files

PowerShell: Working with CSV Files

Why did I want to do this?

I wanted to learn how to manipulate CSV files for logging purposes as well as for part of a script I was writing to email out a different quest each day. 

In particular I wanted to see if today was completed, and if not, update it to say so and then add the next day with Completed = No.

The setup

This function is part of a larger script that I'll write a post about after covering more of the basics.

You'll need the following things to get it running:

A file named runlog.csv in the same folder with the following contents:

“DateTime”,”Day”,”Completed”
”1/18/2016”,”1”,”No”

Preferably you'll want the first DateTime to match the current date, and day to start at 1. The script should be able to catch a gap in the DateTime and continue on, however.

Now you'll need to setup the following variables:

$todayDate        = (Get-Date).ToShortDateString()
$tomorrowDate     = (Get-Date).AddDays(1).ToShortDateString()
$runLog           = Import-CSV .\runlog.csv
$logHeaders       = @{
    "DateTime"  = '' 
    "Day"       = ''
    "Completed" = '' 
}

How to call the function

You'll want to call the function like so:

if ($updateLog)   {Log-DayQuest $todayDate $tomorrowDate $logHeaders}

At the beginning of the script you'd want something like:

[cmdletbinding()]
Param(
    [boolean]
    $updateLog
)

The code (let's put it all together!)

[cmdletbinding()]
Param(
    [boolean]
    $updateLog
)

$todayDate        = (Get-Date).ToShortDateString()
$tomorrowDate     = (Get-Date).AddDays(1).ToShortDateString()
$runLog           = Import-CSV .\runlog.csv
$logHeaders       = @{
    "DateTime"  = '' 
    "Day"       = ''
    "Completed" = '' 
} 

function Log-DayQuest {
    [cmdletbinding()]
    param($todayDate,$tomorrowDate,$logHeaders)
    
    [int]$questDay   = ($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Day).Day
    
        if (($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Completed).Completed -eq "Yes") {
    
            Write-Host "Log already updated!"
 
        } Elseif ($runLog | Where-Object {$_.DateTime -eq $todayDate})  { 

            [int]$day = ($runLog | Where-Object {$_.DateTime -eq $todayDate} | Select-Object Day).Day
        
            #Log today as completed
            ($runLog | Where-Object {$_.DateTime -eq $todayDate}).Completed = "Yes"        

            $runLog | Export-CSV .\runlog.csv -NoTypeInformation
          
            #Log tomorrow as not completed
            
            $logHeaders.DateTime  = $tomorrowDate
            $logheaders.Day       = $day+1
            $logheaders.Completed = "No"

            $newrow = New-Object PSObject -Property $logHeaders
            Export-CSV .\runLog.csv -InputObject $newrow -append -Force
            
            Write-Host "Log updated!"
        
        } elseif($runLog | Where-Object {$_.DateTime -eq $todayDate} -eq $null) {
            
            Write-Host "No entry for today... creating entry and updating"
            [int]$day = ($runlog[$runlog.count-1]).day 
            $logHeaders.DateTime  = $todayDate
            $logheaders.Day       = $day+1
            $logheaders.Completed = "Yes"
            
            $newrow = New-Object PSObject -Property $logHeaders
            Export-CSV .\runLog.csv -InputObject $newrow -append -Force

        }
}

if ($updateLog)   {Log-DayQuest $todayDate $tomorrowDate $logHeaders}

The results

Here are the results of me testing the script.

More on how it works coming up!

Please let me know what you think or if you have a quicker way to accomplish the same thing.

One of the things I love about PowerShell are the different ways to accomplish the same thing. That's the best way to learn. 

PowerShell: Working with the NetApp module (part 2) 7-Mode

PowerShell: Working with the NetApp module (part 2) 7-Mode

A couple weeks ago I shared part of a script that enables you to separate out the connection to 7-Mode and CDOT controllers.

If you missed that, check it out!

Today I will share the part that expands upon what you can do with the 7-Mode information.

The Code:

Function Get-7ModeInfo {


param ($naCredentials,
       $naController)
    
    if (Get-NASystemVersion) {
        $netAppSystemInfo = Get-NASystemInfo

        Write-Host `n"Controller information (7 Mode)" -ForegroundColor $foregroundColor

        Write-Host   "System Name      : " $netAppSystemInfo.SystemName 
        Write-Host   "System ID        : " $netAppSystemInfo.SystemId
        Write-Host   "System Revision  : " $netAppSystemInfo.SystemRevision 
        Write-Host   "System Serial    : " $NetAppSystemInfo.SystemSerialNumber
    
        Write-Host `n"Partner Information" -ForegroundColor $foregroundColor

        Write-Host   "Partner          : " $netAppSystemInfo.PartnerSystemName 
        Write-Host   "Partner System ID: " $netAppSystemInfo.PartnerSystemId

        Write-Host `n"Options..." -ForegroundColor $foregroundColor
        Write-Host "1. Reset your password"
        Write-Host "2. Search through volumes"
        Write-Host "3. Volume Space Report"
        Write-Host "4. Lun Report"`n
        Write-Host "'q' drops to shell"`n

        $selection = Read-Host "Which #?"
        
        Switch ($selection) {

            1 {
    
                $userName     = $NetAppCredential.UserName 
                $oldPass      = $netAppCredential.Password | ConvertFrom-SecureString        
                $newPassword  = Get-Credential -Message "Enter new password below" -UserName "Doesn't Matter"
                $newdPassword = $newPassword.GetNetworkCredential().Password

                Set-NaUserPassword -User $userName -OldPassword $oldPass -NewPassword $newdPassword
                
                Get-7ModeInfo

            }

            2 {

            }

            3{ 

            Get-NaVol | Select @{Name="VolumeName";Expression={$_.name}},@{Name="TotalSize(GB)";Expression={[math]::Round([decimal]$_.SizeTotal/1gb,2)}},@{Name="AvailableSize(GB)";Expression={[math]::Round([decimal]$_.SizeAvailable/1gb,2)}},@{Name="UsedSize(GB)";Expression={[math]::Round([decimal]$_.SizeUsed/1gb,2)}},@{Name="SnapshotBlocksReserved(GB)";Expression={[math]::Round([decimal]$_.SnapshotBlocksReserved/1gb,2)}},SnapshotPercentReserved,Aggregate,IsDedupeEnabled,type,DiskCount,RaidStatus,Autosize,ChecksumStyle,state | Export-Csv -LiteralPath $Location\$nacontroller.csv -Force -NoTypeInformation -Verbose
            Start-Process .\$nacontroller.csv
            
            Get-7ModeInfo
            
            }

            4{

            Get-NaLun | Select Path,@{Name="TotalSize(GB)";Expression={[math]::Round([decimal]$_.TotalSize/1gb,2)}},@{Name="UsedSize(GB)";Expression={[math]::Round([decimal]$_.SizeUsed/1gb,2)}},Protocol,Online,Thin,Comment | Export-Csv -LiteralPath $Location\$nacontroller"luns".csv -Force -NoTypeInformation -Verbose
            Start-Process .\$nacontroller"luns".csv
            
            Get-7ModeInfo

            }
             
            q {Write-Host "I see 'q', dropping to shell..." -foregroundcolor $foregroundcolor;break}

            }
        }
}

This is very basic and some of the switch options are not even set up yet. I used it as a shell to get some ideas for further administration. Coming up next will will be the same thing but with CDOT commands!

As always, let me know if you have any ideas or comments.

Get VMware Guest OS List With PowerCLI

Get VMware Guest OS List With PowerCLI

Get VMware Guest OS List With PowerCLI

What it does...

This function I wrote will return an object which contains the unique name of each OS for guests on a vCenter server. It will also show you the total number of VMs with that OS.

What it needs to run...

This function takes the parameter $vCenter. This should be the name of a vCenter server in your environment. You can also call the function as part of a foreach loop if you have multiple vCenter servers, running it once for each server and then returning that into another variable.

The Code:

function Get-VMOSList {
    [cmdletbinding()]
    param($vCenter)
    
    Connect-VIServer $vCenter  | Out-Null
    
    [array]$osNameObject       = $null
    $vmHosts                   = Get-VMHost
    $i = 0
    
    foreach ($h in $vmHosts) {
        
        Write-Progress -Activity "Going through each host in $vCenter..." -Status "Current Host: $h" -PercentComplete ($i/$vmHosts.Count*100)
        $osName = ($h | Get-VM | Get-View).Summary.Config.GuestFullName
        [array]$guestOSList += $osName
        Write-Verbose "Found OS: $osName"
        
        $i++    
 
    
    }
    
    $names = $guestOSList | Select-Object -Unique
    
    $i = 0
    
    foreach ($n in $names) { 
    
        Write-Progress -Activity "Going through VM OS Types in $vCenter..." -Status "Current Name: $n" -PercentComplete ($i/$names.Count*100)
        $vmTotal = ($guestOSList | ?{$_ -eq $n}).Count
        
        $osNameProperty  = @{'Name'=$n} 
        $osNameProperty += @{'Total VMs'=$vmTotal}
        $osNameProperty += @{'vCenter'=$vcenter}
        
        $osnO             = New-Object PSObject -Property $osNameProperty
        $osNameObject     += $osnO
        
        $i++
    
    }    
    Disconnect-VIserver -force -confirm:$false
        
    Return $osNameObject
}

Here is some example output (I tested it within another script I wrote). 
I blacked out the vCenter server name for security reasons.

 

Here is an example of the code I use to wrap it:

In this example the variables that already exist are:

$vCenterServers (this is an array of my vCenter servers)
$vCenter              (this is a parameter of my main script, the value of "all" is what the example below shows)
$getGuestOSList (this is a boolean parameter my script uses. What you see below is what happens if it is $true)

The below code will go through each of the individual vCenter servers and use the function above to build the variable $guestOSObjects with the output.

if (Get-View $DefaultViserver.ExtensionData.Client.ServiceContent.SessionManager) {Disconnect-VIServer * -confirm:$false -force;Write-Host "Disconnecting VI Server Sessions" -foregroundColor $foregroundColor}

if ($getGuestOSList) {
    
    Switch ($vCenter) {
    {$_ -like "all"} {
        
        $i = 0
        
        Foreach ($v in $vCenterServers) {
           
            $vCenter = $vcenterServers[$i]
            Write-Progress -Activity "Looking up VM Guests" -Status "Current vCenter Server: $vCenter" -PercentComplete ($i/$vCenterServers.Count*100)
            [array]$GuestOSObjects += Get-VMOSList $vCenter
      
            $i++
        
        }
        
    Return $guestOSOBjects
    
    }
    
}

PowerShell: Working with the NetApp module (part 1)

PowerShell: Working with the NetApp module (part 1)

I've been tasked with heading up a storage migration where I am currently employed.

We have both 7-Mode and CDOT systems here (NetApp), and I wanted a way to run different functions with varying options based on each OS. 

To do this I created a couple variables up front:

$accountRun and $NetAppControllers.

$accountRun simply gets the account name you're running PowerShell as.

$accountRun               = (Get-ChildItem ENV:username).Value

$netAppControllers is an array of your controllers.

$global:NetAppControllers = @("controller1","controller2-c")

Notice how controller2 has -c after it. This is used in the below code to delineate between 7-Mode and CDOT controllers.

function Connect-NetAppController {

    $i = 0
    Write-Host `n"NetApp Controllers"`n -ForegroundColor $foregroundColor

    foreach ($n in $NetAppControllers) { 
    
        if ($n.Split("-")[1] -like "*c*"){

            Write-Host $i -NoNewline
            Write-Host -> -NoNewLine -ForegroundColor $foregroundcolor 
            Write-Host $n.Split("-")[0]  -NoNewline
            Write-Host "(CDOT)" -ForegroundColor $foregroundColor

        } else {

            Write-Host $i -NoNewline
            Write-Host -> -NoNewLine -ForegroundColor $foregroundcolor 
            Write-Host $n 

        }

        $i++

}
    Write-Host `n

    $selection = Read-Host "Which #?"
    
    if (($netAppControllers[$selection]).Split("-")[1] -like "*c*") { 
    Write-Host ($netAppControllers[$selection]).Split("-")[0]
        $nController = $netAppControllers[$selection].Split("-")[0]
        
        Write-Host `n"Attempting to connect to: " $nController "(CDOT)"`n -ForegroundColor $foregroundColor
        
        if (Test-Connection $nController -Count 1 -Quiet) {
            
        Write-Host `n"We are able to connect to:" $nController "... gathering credentials."`n -foregroundcolor $foregroundColor
        
        $netAppCredential = Get-Credential -UserName $accountRun.ToLower() -Message "Enter NetApp Credentials for $nController"
       
        Connect-NcController $nController -Credential $netAppCredential | Out-Null

        Get-CDOTInfo $netAppCredential $nController
    
        } Else {
            
            Write-Host `n"Unable to ping: " $nController`n -foregroundcolor Red
            
        }

    } else {

        $7Controller = $NetAppControllers[$selection]
        
        Write-Host `n"Attempting to connect to: " $7Controller`n -ForegroundColor $foregroundColor

        if (Test-Connection $NetAppControllers[$selection] -Count 1 -Quiet) {
        
        $7Controller = $NetAppControllers[$selection]
            
        Write-Host `n"We are able to connect to:" $7Controller "... gathering credentials."`n -foregroundcolor $foregroundColor 
            
        $netAppCredential = Get-Credential -UserName $accountRun.ToLower() -Message "Enter NetApp Credentials for $nController"

        Connect-NaController $NetAppControllers[$selection] -Credential $netAppCredential | Out-Null

        Get-7ModeInfo $netAppCredential $NetAppControllers[$selection]
       
         } Else {
            
            Write-Host `n"Unable to ping: " $7Controller`n -foregroundcolor Red
            
        }
         
     }
  }

Let me know what you think, xor you have any ideas!

PowerShell: Get-DCDiag script for getting domain controller diagnostic information

PowerShell: Get-DCDiag script for getting domain controller diagnostic information

I wrote a script that will run DCDiag on domain controllers that you specify or all DCs in your environment. 

I will be working to improve the script as much as I can. This was a way for me to learn how advanced parameters and object building works in PowerShell.

The "all" and "full" options currently return an object that you can manipulate. The other options will do that soon once I update the script and test it more.

<#   
.SYNOPSIS   
   Display DCDiag information on domain controllers.
.DESCRIPTION 
   Display DCDiag information on domain controllers. $adminCredential and $ourDCs should be set externally.
   $ourDCs should be an array of all your domain controllers. This function will attempt to set it if it is not set via QAD tools.
   $adminCredential should contain a credential object that has access to the DCs. This function will prompt for credentials if not set.
   If the all dc option is used along side -Type full, it will return an object you can manipulate.
.PARAMETER DC 
    Specify the DC you'd like to run dcdiag on. Use "all" for all DCs.
.PARAMETER Type 
    Specify the type of information you'd like to see. Default is "error". You can specify "full"           
.NOTES   
    Name: Get-DCDiagInfo
    Author: Ginger Ninja (Mike Roberts)
    DateCreated: 12/08/2015
.LINK  
    https://www.gngrninja.com/script-ninja/2015/12/29/powershell-get-dcdiag-commandlet-for-getting-dc-diagnostic-information      
.EXAMPLE   
    Get-DCDiagInfo -DC idcprddc1 -Type full
    $DCDiagInfo = Get-DCDiagInfo -DC all -type full -Verbose
#>  
    [cmdletbinding()]
    param(
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [String]
        $DC,
        
        [Parameter()]
        [ValidateScript({$_ -like "Full" -xor $_ -like "Error"})]
        [String]
        $Type,
        
        [Parameter(Mandatory=$false,ValueFromPipeline=$false)]
        [String]
        $Utility
        )
    
    try {
        
    if (!$ourDCs) {
        
        $ourDCs = get-QADComputer -computerRole 'DomainController'
    
    }
    
    if (!$adminCredential) {
        
        $adminCredential = Get-Credential -Message "Please enter Domain Admin credentials"
        
    }
    
    Switch ($dc) {
    
    {$_ -eq $null -or $_ -like "*all*" -or $_ -eq ""} {
    
        Switch ($type) {  
            
        {$_ -like "*error*" -or $_ -like $null} {  
             
            [array]$dcErrors = $null
            $i               = 0
            
            foreach ($d in $ourDCs){
            
                $name = $d.Name    
                
                Write-Verbose "Domain controller: $name"
                
                Write-Progress -Activity "Connecting to DC and running dcdiag..." -Status "Current DC: $name" -PercentComplete ($i/$ourDCs.Count*100)
                
                $session = New-PSSession -ComputerName $d.Name -Credential $adminCredential
                
                Write-Verbose "Established PSSession..."
                
                $dcdiag  = Invoke-Command -Session $session -Command  { dcdiag }
                
                Write-Verbose "dcdiag command ran via Invoke-Command..."
            
                if ($dcdiag | ?{$_ -like "*failed test*"}) {
                    
                    Write-Verbose "Failure detected!"
                    $failed = $dcdiag | ?{$_ -like "*failed test*"}
                    Write-Verbose $failed
                    [array]$dcErrors += $failed.Replace(".","").Trim("")
            
                } else {
                
                    $name = $d.Name    
                
                    Write-Verbose "$name passed!"
                    
                }
                
                
                Remove-PSSession -Session $session
                
                Write-Verbose "PSSession closed to: $name"
                $i++
            }
            
            Return $dcErrors
        } 
            
        {$_ -like "*full*"}    {
            
            [array]$dcFull             = $null
            [array]$dcDiagObject       = $null
            $defaultDisplaySet         = 'Name','Error','Diag'
            $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultDisplaySet)
            $PSStandardMembers         = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
            $i                         = 0
            
            foreach ($d in $ourDCs){
                
                $diagError = $false
                $name      = $d.Name
                
                Write-Verbose "Domain controller: $name"
                
                Write-Progress -Activity "Connecting to DC and running dcdiag..." -Status "Current DC: $name" -PercentComplete ($i/$ourDCs.Count*100)
                
                $session = New-PSSession -ComputerName $d.Name -Credential $adminCredential
                
                Write-Verbose "Established PSSession..."
                
                $dcdiag  = Invoke-Command -Session $session -Command  { dcdiag }
                
                Write-Verbose "dcdiag command ran via Invoke-Command..."
                
                $diagstring = $dcdiag | Out-String
                
                Write-Verbose $diagstring
                if ($diagstring -like "*failed*") {$diagError = $true}
                
                $dcDiagProperty  = @{Name=$name}
                $dcDiagProperty += @{Error=$diagError}
                $dcDiagProperty += @{Diag=$diagstring}
                $dcO             = New-Object PSObject -Property $dcDiagProperty
                $dcDiagObject   += $dcO
                
                Remove-PSSession -Session $session
                
                Write-Verbose "PSSession closed to: $name"
                
                $i++
            }
            
            $dcDiagObject.PSObject.TypeNames.Insert(0,'User.Information')
            $dcDiagObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers
            
            Return $dcDiagObject
        
            }
        
        }
         break         
    }
   
   
    {$_ -notlike "*all*" -or $_ -notlike $null} {
   
        Switch ($type) {
        
        {$_ -like "*error*" -or $_ -like $null} {
        
            if (Get-ADDomainController $dc) { 
    
                Write-Host "Domain controller: " $dc `n -foregroundColor $foregroundColor
            
                $session = New-PSSession -ComputerName $dc -Credential $adminCredential
                $dcdiag  = Invoke-Command -Session $session -Command  { dcdiag }
       
                if ($dcdiag | ?{$_ -like "*failed test*"}) {
                
                    Write-Host "Failure detected!"
                
                    $failed = $dcdiag | ?{$_ -like "*failed test*"}
                
                    Write-Output $failed 
                
                } else { 
                
                    Write-Host $dc " passed!"
                
                }
                    
            Remove-PSSession -Session $session       
            } 
        }
        
        {$_ -like "full"} {
            
            if (Get-ADDomainController $dc) { 
    
                Write-Host "Domain controller: " $dc `n -foregroundColor $foregroundColor
            
                $session = New-PSSession -ComputerName $dc -Credential $adminCredential
                $dcdiag  = Invoke-Command -Session $session -Command  { dcdiag }
                $dcdiag     
                    
                Remove-PSSession -Session $session       
            }     
                
        }
        
    }
    
    }
    
    }
    
    }
    
    Catch  [System.Management.Automation.RuntimeException] {
      
        Write-Warning "Error occured: $_"
 
        
     }
    
    Finally { Write-Verbose "Get-DCDiagInfo function execution completed."}

You can run this as a script or as a function within a script depending on your needs. Let me know if you have any feedback!