####################################################################################################################### # # Get-OntapVersionFromString - The input parameter $VersionString, is the version string returned from ontap in the form: # [ ]: # eg: NetApp Release 8.0.1 7-Mode: Wed Jan 5 17:23:51 PST 2011 # # can be "NetApp Release " (NetApp version of ONTAP) or "Data ONTAP Release " (IBM version of ONTAP) # # can be in the form "8.2.0", "8.2X45", "8.2.1X45", "SierraNevada__8.2.0" etc. # # In this commandlet we extract the version substring and process it to give a version in the form # :: wherever possible. # ######################################################################################################################### function Get-OntapVersionFromString { <# .SYNOPSIS Cmdlet to get the Data ONTAP version in a 3-figure format. .DESCRIPTION Whereever possible the version substring is extracted and procesed to give a version in the following format ::. .PARAMETER VersionString The version string obtained from Data ONTAP .EXAMPLE Get-OntapVersionFromString -VersionString $ontapVersion #> param ( [parameter(Mandatory=$true)] [string]$VersionString ) if($VersionString -match "^(Netapp Release |Data ONTAP Release )(?[^:\s]+)[^:]*:.*$") { $extractedversion = $matches.version } else { throw "The input version string is not in the expected format." } if($extractedversion -match "^(?\d+)[.](?\d+)[.](?\d+)$") #matches extracted versions like "8.2.0" { $version = $extractedversion } elseif($extractedversion -match "^(?\d+)[.](?\d+)[^\d.][^.]*$") #matches extracted versions like "8.2X45" { $version = $matches.major + "." + $matches.minor + ".0" } elseif($extractedversion -match "^(?\d+)[.](?\d+)[.](?\d+)[^\d.][^.]*$") #matches extracted versions like "8.2.1X45" { $version = $matches.major + "." + $matches.minor + "." + $matches.micro } elseif($extractedversion -match "^.*__(?\d+)[.](?\d+)[.](?\d+)$") #matches extracted versions like "SierraNevada__8.2.0" { $version = $matches.major + "." + $matches.minor + "." + $matches.micro } else { $version = $extractedversion } return $version } ######################################################################################################################## # # Compare-OntapVersions - In this commandlet we take two ontap versions, FirstVersion and SecondVersion in the form: # :: # and return 1 if FirstVersion is a later release of ONTAP than SecondVersion # or return -1 if SecondVersion is a later release of ONTAP than FirstVersion # or return 0 if both versions are the same # # The commandlet will produce an error if both versions are not in the above mentioned format. # Also one can specify parameter IgnoreMicroComparision to be true if we dont want to compare minor versions. # ######################################################################################################################### function Compare-OntapVersions { <# .SYNOPSIS Cmdlet to compare the Data ONTAP version of the array or cluster with the provided version string. .DESCRIPTION This cmdlet takes two Data ONTAP versions (FirstVersion and SecondVersion) in the "::" format and returns: 1 if the FirstVersion is a later release than the SecondVersion -1 if SecondVersion is a later release than the FirstVersion 0 if both versions are the same. The cmdlet will log an error if both versions are not in the above mentioned format. You can also specify the IgnoreMicroComparision parameter "$true", if you do not want to compare minor versions .PARAMETER FirstVersion The first Data ONTAP version in :: format .PARAMETER SecondVersion The second Data ONTAP version in :: format .PARAMETER IgnoreMicroComparision Boolean to include or ignore the in the comparison. .EXAMPLE Compare-OntapVersions -FirstVersion $firstVersion -SecondVersion $secVersion #> param ( [parameter(Mandatory=$true)] [string]$FirstVersion, [parameter(Mandatory=$true)] [string]$SecondVersion, [parameter(Mandatory=$false)] [bool]$IgnoreMicroComparision=$false ) if($FirstVersion -match "^(?\d+)[.](?\d+)[.](?\d+)$") { $major1 = $matches.major $minor1 = $matches.minor $micro1 = $matches.micro } else { throw "The input FirstVersion is not in the expected format." } if($SecondVersion -match "^(?\d+)[.](?\d+)[.](?\d+)$") { $major2 = $matches.major $minor2 = $matches.minor $micro2 = $matches.micro } else { throw "The input SecondVersion is not in the expected format." } if ($major1 -gt $major2) { return 1 } elseif ($major1 -lt $major2) { return -1 } elseif ($minor1 -gt $minor2) { return 1 } elseif ($minor1 -lt $minor2) { return -1 } elseif (! $IgnoreMicroComparision) { if ($micro1 -gt $micro2) { return 1 } elseif ($micro1 -lt $micro2) { return -1 } else { return 0 } } else { return 0 } } ###################################################### # # Invoke-WfaScript - Used instead of Invoke-WfaCommand when Get-Parameter is invoked. # All it does is avoiding sending messages to the log before the command is invoked. # ###################################################### function Invoke-WfaScript { param ( [parameter(Mandatory=$true)] [string]$Script ) Invoke-Expression $Script } ###################################################### # # Invoke-WfaCommand # ###################################################### function Invoke-WfaCommand { param ( [parameter(Mandatory=$true)] [string]$Script ) $mockFile = "C:\temp\workflow_execution_mock.xml" if(Test-Path -Path $mockFile) { try { $workflowName = Get-WfaRestParameter "workflowName" $commandName = Get-WfaRestParameter "commandName" } catch { # no rest parameters if invoked from "test command" feature # just execute it in this case Invoke-Expression $Script return } [xml]$data = Get-Content $mockFile [System.Xml.XmlElement]$mock = Select-Xml -xml $data -XPath "/workflows/workflow[@name='$workflowName']/commands/command[@name='$commandName']" | Select-Object -ExpandProperty Node if($mock) { Get-WFALogger -Info -message "Mock found" Invoke-WfaMock -Mock $mock -Script $Script } else { Get-WFALogger -Info -message "Mock not found" } } else { trap [Exception] { Get-WFALogger -Error -message $_.Exception.Message throw $_.Exception } Invoke-Expression $Script } } ###################################################### # # Get-Parameter # ###################################################### function Get-Parameter { param ( [string]$Cmdlet, [switch]$ShowCommon, [switch]$Full ) ### Validate the Cmdlet first ### $CmdletErrors = $null $Content = Get-Content -Path $Cmdlet [System.Management.Automation.PsParser]::Tokenize($Content,[ref]$CmdletErrors) if($CmdletErrors) { [string]$summary = "" $CmdletErrors | Foreach-Object { $summary = $summary + $_.Message + "`n" } throw $summary } ### End of validation ### $command = Get-Command $Cmdlet -ea silentlycontinue | Sort-Object | Select-Object -First 1 if(($command.ParameterSets | Measure-Object).Count -eq 0) { throw "The script is invalid. Please check the syntax." } # resolve aliases (an alias can point to another alias) while ($command.CommandType -eq "Alias") { $command = Get-Command ($command.definition) } if (-not $command) { throw "Not a command" } foreach ($paramset in $command.ParameterSets) { $Output = @() foreach ($param in $paramset.Parameters) { if ( ! $ShowCommon ) { if ($param.aliases -match "vb|db|ea|wa|ev|wv|ov|ob|wi|cf") { continue } } $process = "" | Select-Object Name, Type, ParameterSet, Aliases, Position, IsMandatory, Pipeline, PipelineByPropertyName, HelpMessage, ValidValues $process.Name = $param.Name if ( $param.ParameterType.Name -eq "SwitchParameter" ) { $process.Type = "Boolean" } else { switch -regex ( $param.ParameterType ) { "Nullable``1\[(.+)\]" { $process.Type = $matches[1].Split('.')[-1] + " (nullable)" ; break } default { $process.Type = $param.ParameterType.Name } } } if ( $paramset.name -eq "__AllParameterSets" ) { $process.ParameterSet = "Default" } else { $process.ParameterSet = $paramset.Name } #process.Aliases = $param.aliases if ( $param.Position -lt 0 ) { $process.Position = $null } else { $process.Position = $param.Position } $process.IsMandatory = $param.IsMandatory $process.Pipeline = $param.ValueFromPipeline $process.PipelineByPropertyName = $param.ValueFromPipelineByPropertyName $process.HelpMessage = $param.HelpMessage [array]$validValues = ($param.Attributes|? {$_.Typeid.Name -eq "ValidateSetAttribute" }).ValidValues # convert PoSH array to string with delimiter if($validValues) { [string]$result = "" for ($i=0; $i -le $validValues.Length - 1; $i++) { $result = $result + $validValues[$i] if($i -lt ($validValues.Length - 1)) { $result = $result + "," } } $process.ValidValues = $result } # convert PoSH array to string with delimiter if($param.aliases) { [string]$result = "" for ($i=0; $i -le $param.aliases.Count - 1; $i++) { $result = $result + $param.aliases[$i] if($i -lt ($param.aliases.Count - 1)) { $result = $result + "," } } $process.Aliases = $result } $output += $process } if ( ! $Full ) { $Output | Select-Object Name, Type, ParameterSet, IsMandatory, Pipeline, HelpMessage, ValidValues, Aliases } else { Write-Output $Output } } } ###################################################### # # Test-CredentialSecure # ###################################################### function Test-CredentialSecure{ param ( [parameter(Mandatory=$true)] [string]$Type, [parameter(Mandatory=$true)] [string]$Ip, [parameter(Mandatory=$false)] [string]$UserName ) $Password = $pscmdlet.GetVariableValue("_Netapp_Uri"); Test-Credential -Type $Type -Ip $Ip -UserName $UserName -Password $Password } ###################################################### # # Test-Credential # ###################################################### function Test-Credential{ param ( [parameter(Mandatory=$true)] [string]$Type, [parameter(Mandatory=$true)] [string]$Ip, [parameter(Mandatory=$false)] [string]$UserName, [parameter(Mandatory=$false)] [string]$Password ) $temp=$Ip.split("@")[1] if($temp) { $Ip = $temp } if($Type -eq "ONTAP") { TestConnection -IP $Ip -Type $Type if($UserName) { if($Password) { # create credentials with password if both, user name and password are provided $SecurePassword = ConvertTo-SecureString $Password -AsPlainText -Force $credentials = New-Object System.Management.Automation.PSCredential ($UserName, $SecurePassword) } else { # create credentials with user name and empty password if only the user name is provided $credentials = New-Object System.Management.Automation.PSCredential ($UserName, (New-Object System.Security.SecureString)) } } try { if ($credentials) { Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Ip + " -ErrorAction Stop") Connect-NaController -Credential $credentials -Name $Ip -ErrorAction Stop } else { Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Ip + " -ErrorAction Stop") Connect-NaController -Name $Ip -ErrorAction Stop } } catch { HandleControllerExceptions -ExceptionMessage $_.Exception.Message -Type "ONTAP" -IP $Ip } } elseif($Type -eq "DFM") { TestConnection -IP $Ip -Type $Type if(!$UserName -or !$Password) { throw "Failed to connect to OnCommand Unified Manager: " + $Ip + ".`nNo credentials were found."; } [NetApp.Manage.NaServer] $zapiServer = New-Object NetApp.Manage.NaServer ($Ip,"1","0") $zapiServer.SetAdminUser($UserName, $Password ) $zapiServer.ServerType = "DFM" try { # OnCommand 6.0 has the default HTTPS port as 443. $zapiServer.TransportType = "HTTPS" $zapiServer.Port = 443 $output = $zapiServer.Invoke("dfm-about") } catch { $errorMessage = $_.Exception.Message; if($errorMessage.indexOf("`n") -ne -1) { # notify user with 1 line of error message only because in many cases we get the stack trace embedded with error message $errorMessage = $errorMessage.substring(0, $errorMessage.indexOf("`n")) } if($errorMessage.indexOf("Incorrect credentials for") -ne -1) { $errorMessage = "Incorrect credentials for " + $Ip; $msg = "Failed to connect to OnCommand Unified Manager: " + $Ip + ".`n" + $errorMessage; throw $msg } try { # Default try, which is for legacy DFM, HTTPS and Port as 8488 $zapiServer.TransportType = "HTTPS" $zapiServer.Port = 8488 $output = $zapiServer.Invoke("dfm-about") } catch { $errorMessage = $_.Exception.Message; if($errorMessage.indexOf("`n") -ne -1) { # notify user with 1 line of error message only because in many cases we get the stack trace embedded with error message $errorMessage = $errorMessage.substring(0, $errorMessage.indexOf("`n")) } if($errorMessage.indexOf("Incorrect credentials for") -ne -1) { $errorMessage = "Incorrect credentials for " + $Ip; $msg = "Failed to connect to OnCommand Unified Manager: " + $Ip + ".`n" + $errorMessage; throw $msg } try { # connection over HTTPS failed - fall back to HTTP with 8088 as default port for legacy DFM $zapiServer.Port = 8088 $zapiServer.TransportType = "HTTP" $output = $zapiServer.Invoke("dfm-about") } catch { $errorMessage = $_.Exception.Message; if($errorMessage.indexOf("`n") -ne -1) { # notify user with 1 line of error message only because in many cases we get the stack trace embedded with error message $errorMessage = $errorMessage.substring(0, $errorMessage.indexOf("`n")) } if($errorMessage.indexOf("Incorrect credentials for") -ne -1) { $errorMessage = "Incorrect credentials for " + $Ip; } $msg = "Failed to connect to OnCommand Unified Manager: " + $Ip + ".`n" + $errorMessage; throw $msg } } } } elseif($Type -eq "VIRTUAL_CENTER") { TestConnection -IP $Ip -Type "VMware vCenter" Test-Connect-WfaVIServer -ViCenterIp $Ip -Type "Test" -UserName $UserName -Password $Password } else { $msg = "Got unexpected type" + $Type throw $msg } } ###################################################### # # Connect-WfaController # ###################################################### function Connect-WfaController{ <# .SYNOPSIS Cmdlet to connect to a Data ONTAP array controller operating in 7-Mode using WFA credentials authentication. .DESCRIPTION The cmdlet is used to connect to a Data ONTAP array controller operating in 7-Mode. Credentials defined in WFA > Execution > "Credentials" are used for the connection. .PARAMETER DisableCaching For using the cached controller connection object. The default value is $false, which means that caching is enabled. .PARAMETER Array Array name or IP address .PARAMETER Timeout Timeout for every Data ONTAP PowerShell Toolkit Cmdlet called on this array after the call to Connect-WfaController in a command definition. The default value is 60000 seconds. .PARAMETER VFiler Name of a vFiler unit in the array on which the Cmdlet or API calls should be invoked. .EXAMPLE Connect-WfaController -Array $myArrayName #> param ( [parameter(Mandatory=$true)] [string]$Array, [parameter(Mandatory=$false)] [string]$VFiler, [parameter(Mandatory=$false)] [int]$Timeout=60000, [parameter(Mandatory=$false)] [switch]$DisableCaching=$False ) if(!$DisableCaching) { $cachedController = Get-CachedController -PrimaryKey $Array -SecondaryKey $Vfiler if($cachedController) { Get-WFALogger -Info -message "Using cached controller connection" # put cached controller on global context $global:CurrentNaController = $cachedController return $cachedController } } # get credentials Get-WFALogger -Info -message $("Get-WfaCredentials -Host " + $Array) $credentials = Get-WfaCredentials -Host $Array # disable EMS report - should speed up the connection process $DataONTAP_SkipEmsReport = 1 try { Connect-Controller -Type ONTAP -Name $Array -Credential $credentials -Vfiler $VFiler -Timeout $Timeout } catch { HandleControllerExceptions -ExceptionMessage $_.Exception.Message -Type "ONTAP" -IP $Array } Get-WFALogger -Info -message "Connected to controller"; if(!$DisableCaching) { Add-CachedController -Controller $global:CurrentNaController -PrimaryKey $Array -SecondaryKey $VFiler } } ###################################################### # # Connect-WfaCluster # ###################################################### function Connect-WfaCluster{ <# .SYNOPSIS Cmdlet to connect to a Data ONTAP cluster using WFA credentials authentication. .DESCRIPTION The cmdlet is used to connect to a Data ONTAP cluster. Credentials defined in WFA > Execution > "Credentials" are used for the connection. .PARAMETER DisableCaching To use the cached controller connection object. Default value is $false i.e. Caching is enabled. .PARAMETER Node Cluster name or IP address .PARAMETER Timeout Timeout for every Data ONTAP PowerShell Toolkit Cmdlet called on this array after the call to Connect-WfaController in a command definition. The default value is 60000 seconds. .PARAMETER Vserver Name of a Storage Virtual Machine in the cluster on which the cmdlet or API calls should be invoked. .EXAMPLE Connect-WfaCluster -Node $myClusterName #> param ( [parameter(Mandatory=$true)] [string]$Node, [parameter(Mandatory=$false)] [string]$Vserver, [parameter(Mandatory=$false)] [int]$Timeout=60000, [parameter(Mandatory=$false)] [switch]$DisableCaching=$False ) if(!$DisableCaching) { $cachedController = Get-CachedController -PrimaryKey $Node -SecondaryKey $Vserver if($cachedController) { Get-WFALogger -Info -message "Using cached controller connection" # put cached controller on global context $global:CurrentNcController = $cachedController return $cachedController } } # get credentials Get-WFALogger -Info -message $("Get-WfaCredentials -Host " + $Node) $credentials = Get-WfaCredentials -Host $Node try { Get-WFALogger -Info -message "Connect-Controller -Type CLUSTER -Name $Node -Credential $credentials -Vserver $Vserver -Timeout $Timeout" Connect-Controller -Type CLUSTER -Name $Node -Credential $credentials -Vserver $Vserver -Timeout $Timeout } catch { HandleControllerExceptions -ExceptionMessage $_.Exception.Message -Type "CLUSTER" -IP $Node } Get-WFALogger -Info -message "Connected to cluster node"; if(!$DisableCaching) { Add-CachedController -Controller $global:CurrentNcController -PrimaryKey $Node -SecondaryKey $Vserver } } ###################################################### # # Set-WfaAcl # ###################################################### function Set-WfaAcl { param( [parameter(Mandatory=$true)] [string]$Array, [parameter(Mandatory=$true)] [ValidateSet("snapvault", "snapmirror")] [string]$Relation ) $optionName = $Relation + ".access" $existingValue = (Get-NaOption -OptionNames $optionName).Value [string]$optionValue = $null if($existingValue) { # see http://now.netapp.com/NOW/knowledge/docs/ontap/rel733/html/ontap/cmdref/man8/na_protocolaccess.8.htm if(($existingValue -eq "host=all") -or ($existingValue -eq "all") -or ($existingValue -eq "*") -or ($existingValue -eq "host=*")) { # do nothing since all hosts are allowed to access } elseif($Relation -eq "snapmirror" -and $existingValue -eq "legacy") { # Check to see if snapmirror.allow is legacy # If so, check if snapmirror.allow contains entries $root = Get-NaVolRoot $path = "/vol/"+$root.name+"/etc/snapmirror.allow" $allow = Read-NaFile -Path $path -EA SilentlyContinue if( $allow -ne $null) { $lines = $allow.Split("`n") $haveEntry = $false foreach ($line in $lines) { if ( $line -match '^\s*[0-9a-zA-Z]') { $haveEntry = $true if($line -eq $Array) { return } } } # If it does have entries, append the requested address if ($haveEntry) { Write-NaFile -Path $path -Offset $allow.length -Data "$Array`n" return } else { $optionValue = "host=$Array" } } else { $optionValue = "host=$Array" } } elseif($Relation -ne "snapmirror" -and (($existingValue -eq "none") -or ($existingValue -eq "-") -or ($existingValue -eq "legacy"))) { $optionValue = "host=$Array" } else { $split = $existingValue.split("=") [bool]$append = $true foreach($entry in $split[1].split(",")) { if($entry -eq $Array) { $append = $false } } if($append) { $newValue = $existingValue + "," + $Array $optionValue = $newValue } } } else { $optionValue = "host=$Array" } if($optionValue) { Set-NaOption $optionName $optionValue } } ###################################################### # # Connect-WfaDfm # ###################################################### function Connect-WfaDfm{ <# .SYNOPSIS Cmdlet to connect to an OnCommand Unified Manager server using credentials authentication. .DESCRIPTION The cmdlet is used to connect to a Unified Manager 5.X or 6.X server. Credentials defined in WFA > Execution > "Credentials" are used for the connection. .PARAMETER Dfm Unified Manager server name or IP address .EXAMPLE Connect-WfaDfm -Dfm $myOCUM #> param ( [parameter(Mandatory=$true)] [string]$Dfm ) $credentials = Get-WfaCredentials -Host $Dfm if(!$credentials) { throw "Failed to connect to OnCommand Unified Manager: " + $Dfm + ".`nNo credentials were found."; } $clearPassword = ConvertFromSecureToPlain -SecurePassword $credentials.Password Test-Credential -Type "DFM" -Ip $Dfm -UserName $credentials.UserName -Password $clearPassword # add credential to cache Add-NaCredential -Credential $credentials -Name $Dfm Get-WfaLogger -Info -message $("Added credentials for " + $Dfm + " to the credential cache") } ###################################################### # # Connect-WfaVIServer # ###################################################### function Connect-WfaVIServer{ <# .SYNOPSIS Cmdlet to connect to a VMware vCenter Server using credentials authentication. .DESCRIPTION The cmdlet is used to connect to a VMware vCenter Server. Credentials defined in WFA > Execution > "Credentials" are used for the connection. You must have VMware Power CLI on the WFA server before using the cmdlet. .PARAMETER ViCenterIp VMware vCenter Server name or IP address .EXAMPLE Connect-WfaDfm -ViCenterIp $myVC #> param ( [parameter(mandatory=$true)] [String]$ViCenterIp ) Test-Connect-WfaVIServer -ViCenterIp $ViCenterIp -Type "Connect" } ###################################################### # # Test-Connect-WfaVIServer # ###################################################### function Test-Connect-WfaVIServer{ param ( [parameter(mandatory=$true)] [String]$ViCenterIp, [parameter(mandatory=$true)] [String]$Type, [parameter(mandatory=$false)] [String]$UserName, [parameter(mandatory=$false)] [String]$Password ) # load VMware module if(-not (Get-PSSnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue)) { Add-PSSnapin VMware.VimAutomation.Core } if($Type -eq "Connect") { Get-WFALogger -Info -message $("Get-WfaCredentials -Host " + $ViCenterIp) $credentials = Get-WfaCredentials -Host $ViCenterIp if(!$credentials) { throw "Failed to connect to VMware vCenter: " + $ViCenterIp + ".`nNo credentials were found."; } # connect to VI server using provided credentials Get-WFALogger -Info -message $("Connect-VIServer -Server " + $ViCenterIp + " -Protocol https") } else { if(!$UserName -or !$Password) { throw "Failed to connect to VMware vCenter: " + $ViCenterIp + ".`nNo credentials were found."; } $securePassword = ConvertTo-SecureString $Password -AsPlainText -Force $credentials = New-Object System.Management.Automation.PSCredential ($UserName, $securePassword) } # common code for both test and connect try { Connect-VIServer -Server $ViCenterIp -Protocol https -Credential $credentials -WarningAction SilentlyContinue -ErrorAction Stop } catch { if($_.Exception.Message.Contains("Authorize Exception") -or $_.Exception.Message.Contains("Cannot complete login due to an incorrect user name or password.")) { $msg = "Failed to connect to VMware vCenter: " + $ViCenterIp + ".`nIncorrect credentials for " + $ViCenterIp; } elseif($_.Exception.Message.Contains("Network connectivity error occurred")) { $msg = "Failed to connect to VMware vCenter : " + $IP + ".`n"; } else { $msg = "Failed to connect to VMware vCenter : " + $IP + ".`n" + $ExceptionMessage; } throw $msg } } ###################################################### ###################################################### # # New-WfaZapiServer # ###################################################### function New-WfaZapiServer{ <# .SYNOPSIS Cmdlet to create a new ZAPI server object. .DESCRIPTION The cmdlet creates a ZAPI object from which Data ONTAP or Unified Manager ZAPIs can be called. .PARAMETER Host The server hostname or IP address .PARAMETER Port The port number to connet to. For HTTPS, use 443 .PARAMETER Type The connecting server type. Valid values are FILER for Data ONTAP and DFM for a Unified Manager server. .PARAMETER VFiler The vFiler unit name in the Filer. Used with the type "FILER" for running ZAPIs on the specified vFiler unit. .EXAMPLE New-WfaZapiServer -Host $DFMServerName -Type DFM -Credentials $credentials #> param ( [parameter(Mandatory=$true)] [string]$Host, [parameter(Mandatory=$false)] [int]$Port, [parameter(Mandatory=$true)] [ValidateSet("FILER", "DFM")] [string]$Type, [parameter(Mandatory=$true)] [System.Management.Automation.PSCredential]$Credentials, [parameter(Mandatory=$false)] [string]$VFiler ) if($Type -eq "DFM") { [NetApp.Manage.NaServer] $zapiServer = New-Object NetApp.Manage.NaServer ($Host,"1","0") } else { [NetApp.Manage.NaServer] $zapiServer = New-Object NetApp.Manage.NaServer ($Host,"1","14") if ($VFiler -and $VFiler.Length -gt 0) { $zapiServer.SetVfilerTunneling($VFiler) } } $clearPassword = ConvertFromSecureToPlain -SecurePassword $Credentials.Password $zapiServer.SetAdminUser($Credentials.UserName, $clearPassword ) $zapiServer.ServerType = $Type if ($Port) { $zapiServer.port = $Port } $zapiServer.TransportType = 'HTTPS' $result = checkConnection -Server $zapiServer -Type $Type if($result -eq 0) { # connection over HTTPS failed - fall back to HTTP $zapiServer.TransportType = 'HTTP' # chech the connection again $result = checkConnection -Server $zapiServer -Type $Type if($result -eq 0) { throw "Failed connecting to host '" + $Host + "' using both http and https" } } return $zapiServer } ###################################################### # # Invoke-WfaCli # ###################################################### function Invoke-WfaCli { param( [parameter(Mandatory=$true)] [string]$CliCommand, [parameter(Mandatory=$false)] [NetApp.Ontapi.Filer.NaController]$Controller ) Get-WFALogger -Info -message "Executing cli command: $CliCommand" $cliXmlRequest = "" $arrCliArgs = $CliCommand.Split(" "); foreach($arg in $arrCliArgs) { if($arg -and $arg -ne "") { $cliXmlRequest+="" + $arg + "" } } $cliXmlRequest += "" try { if($Controller) { $xmlResult = Invoke-NaSystemApi -Request $cliXmlRequest -Controller $Controller -ErrorAction Stop } else { # if $global:CurrentNaController is not populated (session not connected to any filer) an exception is thrown like # original Netapp POSH Toolkit Cmdlet $xmlResult = Invoke-NaSystemApi -Request $cliXmlRequest -ErrorAction Stop } # Examining the cli execution's return code from the storage system if($xmlResult.results."status" -ne "passed") { throw "Cli XML request failed to pass" } # if xml request passed -> examining result value flag elseif($xmlResult.results."cli-result-value" -ne 1) { throw $xmlResult.results."cli-output" } Get-WFALogger -Info -message "Cli command completed successfully" if($xmlResult.results."cli-output".Trim() -ne "") { Get-WFALogger -Info -message ("Cli output: " + $xmlResult.results."cli-output") return $xmlResult.results."cli-output" } else { return "Cli command completed successfully" } } catch { Get-WFALogger -Error -message $_.Exception.Message throw $_.Exception } } ###################################################### ###################################################### # # Invoke-WfaClusterCli # ###################################################### function Invoke-WfaClusterCli { param( [parameter(Mandatory=$true)] [string]$CliCommand, [parameter(Mandatory=$false)] [NetApp.Ontapi.Filer.C.NcController]$Controller, [parameter(Mandatory=$false)] [string]$PrivilegeMode ) Get-WFALogger -Info -message "Executing cli command: $CliCommand" $cliXmlRequest = "" $arrCliArgs = $CliCommand.Split(" "); foreach($arg in $arrCliArgs) { if($arg -and $arg -ne "") { $cliXmlRequest+="" + $arg + "" } } $cliXmlRequest += "" if($PrivilegeMode) { $cliXmlRequest += "$PrivilegeMode" } $cliXmlRequest += "" try { if($Controller) { if($Controller.Vserver) { throw "Cannot execute system-cli api in a Vserver context" } $xmlResult = Invoke-NcSystemApi -Request $cliXmlRequest -Controller $Controller -ErrorAction Stop } else { # if $global:CurrentNaController is not populated (session not connected to any filer) an exception is thrown like # original Netapp POSH Toolkit Cmdlet if($global:CurrentNcController.Vserver) { throw "Cannot execute system-cli api in a Vserver context" } $xmlResult = Invoke-NcSystemApi -Request $cliXmlRequest -ErrorAction Stop } # Examining the cli execution's return code from the storage system if($xmlResult.results."status" -ne "passed") { throw "Cli XML request failed to pass" } # if xml request passed -> examining result value flag elseif($xmlResult.results."cli-result-value" -ne 1) { throw $xmlResult.results."cli-output" } Get-WFALogger -Info -message "Cli command completed successfully" Get-WFALogger -Info -message $("Cli output: " + $xmlResult.results."cli-output") return $xmlResult.results."cli-output" } catch { Get-WFALogger -Error -message $_.Exception.Message throw $_.Exception } } function checkConnection{ param ( [parameter(Mandatory=$true)] [NetApp.Manage.NaServer]$Server, [parameter(Mandatory=$true)] [ValidateSet("FILER", "DFM")] [string]$Type ) # invoke a command in order to verify connection # need to invoke a different call if connecting to DFM or to a filer if ($Type -eq "DFM") { try { $output = $Server.Invoke("dfm-about") } catch { # failed to establish connection return 0 } } else { try { $output = $Server.invoke("system-get-version") } catch { # failed to establish connection return 0 } } # successfully connected return 1 } function ConvertFromSecureToPlain { <# .SYNOPSIS Cmdlet to decrypt a string in the System.Security.SecureString object to plain-text. .DESCRIPTION The cmdlet is used to decrypt a string from the System.Security.SecureString object to plain text. You can use the cmdlet after Get-WfaInputPassword cmdlet, which returns the password as the System.Security.SecureString object, to obtain the password in plain text. .PARAMETER SecurePassword The Secure password as System.Security.SecureString object. .EXAMPLE ConvertFromSecureToPlain -SecurePassword $SecPasswd #> param( [Parameter(Mandatory=$true)] [System.Security.SecureString] $SecurePassword ) # Create a "password pointer" $PasswordPointer = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurePassword) # Get the plain text version of the password $PlainTextPassword = [Runtime.InteropServices.Marshal]::PtrToStringAuto($PasswordPointer) # Free the pointer [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($PasswordPointer) # Return the plain text password $PlainTextPassword } function HandleControllerExceptions { param( [Parameter(Mandatory=$true)] [string]$ExceptionMessage, [parameter(Mandatory=$true)] [string]$Type, [parameter(Mandatory=$true)] [string]$IP ) if($type -eq "ONTAP") { $type = "controller"; } if($type -eq "CLUSTER") { $type = "cluster node"; } if(($ExceptionMessage -eq "RPC Error - Access is denied.") -or ($ExceptionMessage.Contains("RPC Error: Access is denied. Code 0x5.")) -or ($ExceptionMessage.Contains("RPC Error: The security context is invalid")) -or ($ExceptionMessage.Contains("Credentials not provided"))) { $msg = "Failed to connect to " + $type + ": " + $IP + ".`nNo credentials were found."; } elseif($ExceptionMessage.Contains("The RPC server is unavailable.")) { $msg = "Failed to connect to " + $type + ": " + $IP + ".`nThe authentication server is unavailable."; } elseif(($ExceptionMessage -eq "API invoke failed.") -or ($ExceptionMessage.Contains("on port 80 for protocol HTTP"))) { $msg = "Failed to connect to " + $type + ": " + $IP + ".`n"; } else { $msg = "Failed to connect to " + $type + ": " + $IP + ".`n" + $ExceptionMessage; } throw $msg } function TestConnection { param( [parameter(Mandatory=$true)] [string]$IP, [parameter(Mandatory=$true)] [string]$Type ) $testConnectionResult = Test-Connection -ComputerName $IP -Count 1 -Quiet -ErrorAction SilentlyContinue if(!$testConnectionResult) { throw $Type + " host '" + $Ip + "' is unreachable" } } ###################################################### # # WaitFor-NcJob # # This function waits and polls for the status of the specified job (jobId) after every specified no of seconds (secondsToWait). # If the job is complete, displays the success message and exits, else displays the error message (errorMessage) and exits. # ###################################################### function WaitFor-NcJob($secondsToWait, $jobId, $errorMessage) { if ($jobId -ne $null) { $jobInfo = $null do { # Sleep for the specified seconds before polling for job Start-Sleep -s $secondsToWait # Poll for the job $jobInfo = Get-NcJob -Id $jobId -Completed } while (!$jobInfo) # If jobState is something other than 'success', then we have a problem. Report it and exit. if ($jobInfo.JobState -ne "success") { throw $errorMessage + " Reason: " + $jobInfo.JobCompletion } else { Get-WFALogger -Info -message $("Job " + $jobId + " succeeded.") } } } function Get-CachedController{ param ( [parameter(Mandatory=$true)] [string]$PrimaryKey, [parameter(Mandatory=$false)] [string]$SecondaryKey ) $executionCachePath = Get-CacheFile if(!$executionCachePath) { return } if(Test-Path $executionCachePath) { # cache for current execution id exists $controllersCache = Import-Clixml -Path $executionCachePath if($controllersCache.ContainsKey($PrimaryKey + $SecondaryKey)) { # primary key/secondary key pair is found in current execution cache - get the controller from the cache $serializedController = $controllersCache.Get_Item($PrimaryKey + $SecondaryKey) return ConvertFrom-SerializedString $serializedController } } } function Add-CachedController{ param ( [parameter(Mandatory=$true)] [object]$Controller, [parameter(Mandatory=$true)] [string]$PrimaryKey, [parameter(Mandatory=$false)] [string]$SecondaryKey ) $executionCachePath = Get-CacheFile if(!$executionCachePath) { return } if(Test-Path $executionCachePath) { # cache for current execution id exists $controllersCache = Import-Clixml -Path $executionCachePath if($controllersCache.ContainsKey($PrimaryKey + $SecondaryKey)) { # have to remove the existing entry before updating otherwise got "Item has already been added" error $controllersCache.Remove($PrimaryKey + $SecondaryKey) } $serializedController = ConvertTo-SerializedString -Input $Controller $controllersCache.Add($PrimaryKey + $SecondaryKey, $serializedController) Export-Clixml -Path $executionCachePath -InputObject $controllersCache } else { # cache for current execution does not exist $serializedController = ConvertTo-SerializedString -Input $Controller $controllersCache = @{($PrimaryKey + $SecondaryKey) = $serializedController} Export-Clixml -Path $executionCachePath -InputObject $controllersCache } } function Get-CacheFile{ # build path to execution controllers cache XML file $controllersCacheDirectory = (Get-Location).Path + "\controllers_cache\" if(!(Test-Path $controllersCacheDirectory)) { # create cache directory if it does not exist # ignore the return value of New-Item # more about it: # In Windows PowerShell, the results of each statement are returned as # output, even without a statement that contains the Return keyword. # # http://technet.microsoft.com/en-us/library/hh847760.aspx $ignored = New-Item -Path $controllersCacheDirectory -ItemType directory -ErrorAction SilentlyContinue } try { $jobId = Get-WfaRestParameter "jobId" } catch { # no job id in case of test command return } return $controllersCacheDirectory + $jobId + ".xml" } function Invoke-WfaMock { param ( [parameter(Mandatory=$true)] $Mock, [parameter(Mandatory=$true)] $Script ) # # Sleep # if($Mock.'sleep-seconds') { Get-WFALogger -Info -message $("Sleep for " + $Mock.'sleep-seconds' + " seconds") Start-Sleep -Seconds $Mock.'sleep-seconds' } # # Handle command progress (wait command) # if($Mock.'command-progress') { Get-WFALogger -Info -message $("Command progress " + $Mock.'command-progress') $Mock.'command-progress'.Split('\w,\w') | ForEach { $currentProgressPercentage = $_.trim(); if($Mock.'sleep-seconds') { Get-WFALogger -Info -message $("Sleep for " + $Mock.'sleep-seconds' + " Seconds") Start-Sleep -Seconds $Mock.'sleep-seconds' } Get-WFALogger -Info -message $("Reporting on '" + $currentProgressPercentage + "%' progress percentage.") Set-WfaCommandProgress -ProgressPercentage $currentProgressPercentage } } # # Handle conditional status # if($Mock.'conditional-status') { # parse command arguments $argumentsMap = @{}; $parsed = [management.automation.psparser]::Tokenize($Script, [ref]$null) # build argument name to argument value map $currentCommandParameter foreach($token in $parsed) { if($token.Type -eq "CommandParameter") { $currentCommandParameter = $token.Content.Substring(1) } if(($token.Type -eq "CommandArgument") -or ($token.Type -eq "Number")) { $argumentsMap.Add($currentCommandParameter, $token.Content) } } foreach($key in $argumentsMap.keys){ $value = $argumentsMap[$key] $condition = $Mock.'conditional-status'.condition |? {$_.'parameter-name' -eq $key -and $_.'parameter-value' -eq $value} if($condition) { if($condition.status -eq "FAILED") { Get-WFALogger -Info -message $("Conditional failure " + $condition.'error-message') throw $condition.'error-message' } if($condition.status -eq "COMPLETED") { Get-WFALogger -Info -message "Conditional success" return } } } } # # Handle command default status # Get-WFALogger -Info -message $("Status " + $Mock.status) if($Mock.status -eq "FAILED") { Get-WFALogger -Info -message $("Error message " + $Mock.'error-message') throw $Mock.'error-message' } } ###################################################### # # Connect-Controller # ###################################################### function Connect-Controller { param ( [parameter(Mandatory=$true)] [string]$Type, [parameter(Mandatory=$true)] [string]$Name, [parameter(Mandatory=$false)] [System.Management.Automation.PSCredential]$Credential, [parameter(Mandatory=$false)] [string]$Vfiler, [parameter(Mandatory=$false)] [string]$Vserver, [parameter(Mandatory=$false)] [int]$Timeout=60000 ) # get the protocol type to connect to the storage controller $protocol = Get-WfaConfiguration -ConfigKey "ontap.connection.protocol" if ($Type -eq "ONTAP"){ $cred_array1 = $Array.split("@") $temp1 = $cred_array1[1] if ( $cred_array1.Count -eq 2 ) { $WFAuser = Get-WfaRestParameter "userName" if ( $WFAuser -Match "@" ) { $WFAuser = $WFAuser.split("@")[0] } if ( $WFAuser -Match "\\" ) { $WFAuser = $WFAuser.split("\")[1] } if ( $WFAuser -ne $cred_array1[0] ) { throw("Incorrect user credentials attempted to connect to Cluster. Try default credentials or contact your WFA Admin"); } else { Get-WFALogger -Info -message "User specific Credentials defined for user $WFAuser" } } if($temp1) { $Array = $temp1 } if ($credentials){ # Connect to controller using credentials if ($VFiler){ if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NaController -Credential $credentials -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NaController -Credential $credentials -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NaController -Credential $credentials -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop } } else { if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NaController -Credential $credentials -Name $Array -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NaController -Credential $credentials -Name $Array -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NaController (with credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NaController -Credential $credentials -Name $Array -Timeout $Timeout -ErrorAction Stop } } } else { # Connect to controller without credentials if ($VFiler){ if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NaController -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NaController -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Vfiler " + $VFiler + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NaController -Name $Array -Vfiler $VFiler -Timeout $Timeout -ErrorAction Stop } } else { if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NaController -Name $Array -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NaController -Name $Array -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NaController (without credentials) -Name " + $Array + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NaController -Name $Array -Timeout $Timeout -ErrorAction Stop } } } } elseif ($Type -eq "CLUSTER"){ $cred_array = $Node.split("@") $temp = $cred_array[1] if ( $cred_array.Count -eq 2 ) { $WFAuser = Get-WfaRestParameter "userName" if ( $WFAuser -Match "@" ) { $WFAuser = $WFAuser.split("@")[0] } if ( $WFAuser -Match "\\" ) { $WFAuser = $WFAuser.split("\")[1] } if ( $WFAuser -ne $cred_array[0] ) { throw("Incorrect user credentials attempted to connect to Cluster. Try default credentials or contact your WFA Admin"); } else { Get-WFALogger -Info -message "User specific Credentials defined for user $WFAuser" } } if($temp) { $Node = $temp } if ($credentials){ # Connect to cluster node using credentials if ($Vserver){ if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Vserver " + $Vserver + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NcController -Credential $credentials -Name $Node -Vserver $Vserver -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Vserver " + $Vserver + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NcController -Credential $credentials -Name $Node -Vserver $Vserver -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Vserver " + $Vserver + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NcController -Credential $credentials -Name $Node -Vserver $Vserver -Timeout $Timeout -ErrorAction Stop } } else { if ($protocol -eq "HttpsOnly"){ Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTPS") Connect-NcController -Credential $credentials -Name $Node -Timeout $Timeout -ErrorAction Stop -HTTPS } elseif ($protocol -eq "HttpOnly"){ Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Timeout " + $Timeout + " -ErrorAction Stop -HTTP") Connect-NcController -Credential $credentials -Name $Node -Timeout $Timeout -ErrorAction Stop -HTTP } else { Get-WFALogger -Info -message $("Connect-NcController (with credentials) -Name " + $Node + " -Timeout " + $Timeout + " -ErrorAction Stop") Connect-NcController -Credential $credentials -Name $Node -Timeout $Timeout -ErrorAction Stop } } } else { throw "Credentials not provided."; } } } ################ # # Invoke-MySqlQuery ################ function Invoke-MySqlQuery { <# .SYNOPSIS Cmdlet to connect to a MySQL database and execute queries. .DESCRIPTION The cmdlet connects to the WFA MySQL database and executes queries using the default WFA credentials and port. This can be used to connect to an OnCommand Unified Manager database or any other MySQL database server using the database user credentials. When the query is executed, it returns the a list of object rows. .PARAMETER Query The SQL query to be executed .PARAMETER HostName The MySQL server hostname or IP address. The default hostname is localhost. .PARAMETER User The database username. By default, the username is "wfa". .PARAMETER Port The database connection port. The default port is 3306. .PARAMETER Password The Password for the Database username. The default password is "Wfa123" .EXAMPLE Invoke-MySqlQuery -Query "SELECT cluster.name AS 'Cluster Name' FROM cm_storage.cluster" #> Param( [Parameter(Mandatory = $true, HelpMessage="Query to Execute")] [string]$Query, [Parameter(Mandatory = $false, HelpMessage="Database Host to connect.")] [string]$HostName="localhost", [Parameter(Mandatory = $false, HelpMessage="Database Username")] [string]$User="wfa", [Parameter(Mandatory = $false, HelpMessage="Database Port")] [string]$Port="3306", [Parameter(Mandatory = $false, HelpMessage="Database Password")] [string]$Password="Wfa123" ) $ConnectionString = "server=" + $HostName + ";port=" + $Port + ";uid=" + $User + ";pwd=" + $Password try { [void][System.Reflection.Assembly]::LoadWithPartialName("MySql.Data") $Connection = New-Object MySql.Data.MySqlClient.MySqlConnection } catch { throw("MySQL .NET Connector not found. Download and install the Connector/Net Windows (x86, 32-bit), MSI Installer and retry") } try { $Connection.ConnectionString = $ConnectionString $Connection.Open() $Command = New-Object MySql.Data.MySqlClient.MySqlCommand($Query, $Connection) $DataAdapter = New-Object MySql.Data.MySqlClient.MySqlDataAdapter($Command) $DataSet = New-Object System.Data.DataSet $DataAdapter.Fill($dataSet, "data") $DataSet.Tables[0] } catch { throw($_.exception) } finally { $Connection.Close() } } ######################## Aliases ################## New-Alias -Name imysql -value Invoke-MySqlQuery -Description "Invoke a MySql query on WFA Server" ###################################################### Export-ModuleMember Get-OntapVersionFromString Export-ModuleMember Compare-OntapVersions Export-ModuleMember Invoke-WfaScript Export-ModuleMember Invoke-WfaCommand Export-ModuleMember Get-Parameter Export-ModuleMember Test-Credential Export-ModuleMember Connect-WfaController Export-ModuleMember Connect-WfaCluster Export-ModuleMember Set-WfaAcl Export-ModuleMember Connect-WfaDfm Export-ModuleMember Connect-WfaVIServer Export-ModuleMember New-WfaZapiServer Export-ModuleMember Get-WfaVersion Export-ModuleMember Invoke-WfaCli Export-ModuleMember Invoke-WfaClusterCli Export-ModuleMember Get-WfaConfiguration Export-ModuleMember Invoke-MySqlQuery Export-ModuleMember ConvertFromSecureToPlain ####################################################### Export-ModuleMember -alias imysql -function Invoke-MySqlQuery ####################################################### $dir = split-path $MyInvocation.MyCommand.Path [Reflection.Assembly]::LoadFile($dir + "\ManageOntap.dll")