Issue Building PowerCLI Mass OVF Export Tool - powershell

Background
I am creating a script, using PowerCLI, to perform mass OVF exports using VMware's ovftool. The script works by performing the following functions:
Through PowerCLI arguments, take in the vCenter address, naming scheme of the VMs to export, and where the OVFs should be exported
Connect to the vCenter instance
Put all VMs that follow the specified naming scheme into a list
Loop through the list and export each VM to an ovf using the ovftool and the built path to the VM
The Script: VMs-to-OVF.ps1
# Take in vCenter address, naming scheme of VMs to be exported, and where the OVFs should be exported
param (
[string]$vcenter = $(throw "
vCenter address required.`n
Example:`n
.\VMs-to-OVF.ps1 -vcenter <192.168.1.200>`n
.\VMs-to-OVF.ps1 -vcenter <vcenter.test.com>"),
[string]$vmNamingScheme = $(throw "
VM naming scheme required.`n
Example:`n
.\VMs-to-OVF.ps1 -vcenter <vcenterIP/DNS> -vmPath </DATACENTER/vm/`n
test/> -vmNamingScheme <test-vm-1>`n
.\VMs-to-OVF.ps1 -vcenter <vcenterIP/DNS> -vmPath </DATACENTER/vm/`n
test/> -vmNamingScheme <test-vm-*>`n"),
[string]$exportLocation = $(throw "
Export location required.`n
Example:`n
.\VMs-to-OVF.ps1 -vcenter <vcenterIP/DNS> -vmPath </DATACENTER/vm/`n
test/> -vmNamingScheme <test-vm-1> -exportLocation 192.168.1.100:\`n
.\VMs-to-OVF.ps1 -vcenter <vcenterIP/DNS> -vmPath </DATACENTER/vm/`n
test/> -vmNamingScheme <test-vm-*> -exportLocation X:\`n")
)
# Connect to vCenter
Connect-VIServer -Server $vcenter
# $VMs is an array of VM names that will be exported
# $vmNamingScheme gives the VM naming pattern we are looking for
$VMs = $(get-vm -name $vmNamingScheme | select name | format-list | out-string)
$VMs = $VMs.replace("Name : ","")
$VMs = $VMs.replace(" ","")
$VMs = $VMs.split("`n")
$VMs = $VMs|?{$_ -ne $VMs[1]}
# This loop iterates through the $VMs array and performs an OVF export, to the location
# specified in $exportLocation, for each VM name in the array
# $vmPath is comprised of the path to the VM and $VM holds the actual VM name
foreach ($VM in $VMs){
if ($VMs -ne $null){
# ***THIS SCRIPT ASSUMES THE get-vmfolderpath SCRIPT IS IN THE SAME DIRECTORY AS ITSELF***
# get the folder path of the VM using the get-vmfolderpath script (this will align with
# the path in vSphere Folders and Templates view)
$vmPath = "get-vm -name $VM | .\get-vmfolderpath.ps1"
&$vmPath
# ***THIS SCRIPT ASSUMES THE DEFAULT INSTALL PATH FOR THE ovftool PROGRAM
# run the ovftool command with the proper arguments
&'C:\Program Files\VMware\VMware OVF Tool\ovftool.exe' vi://$vcenter$vmPath$VM $exportLocation
}
}
Accompanying Script: get-vmfolderpath
param(
[Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
[string]$folderid,
[switch]$moref
)
$folderparent=get-view $folderid
if ($folderparent.name -ne 'vm'){
if($moref){
$path=$folderparent.moref.toString()+'\'+$path
}
else{
$path=$folderparent.name+'\'+$path
}
if ($folderparent.parent){
if($moref){
get-vmfolderpath $folderparent.parent.tostring() -moref
}
else{
get-vmfolderpath($folderparent.parent.tostring())
}
}
}
else {
if ($moref){
return (get-view $folderparent.parent).moref.tostring()+"\"+$folderparent.moref.tostring()+"\"+$path
}
else {
return (get-view $folderparent.parent).name.toString()+'\'+$folderparent.name.toString()+'\'+$path
}
}
Errors
The ovftool command built in each iteration of the for loop will work if you copy and paste the text into the PowerCLI console, but not when run directly from the script. The following errors are produced when the custom ovftool command is run from within the script:
The term 'get-vm -name VM-NAME | .\get-vmfolderpath.ps1' is not
recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included, verify
that the path is correct and try again.At
C:\Users\username\Desktop\VMs-to-OVF.ps1:85 char:4
+ &$vmPath
+ ~~~~~~~
+ CategoryInfo : ObjectNotFound: (get-vm -name CA...mfolderpath.p
s1:String) [], CommandNotFoundException
+ FullyQualifiedErrorId : CommandNotFoundException
Things I Have Checked:
The output of "get-vm -name vm_name | .\get-vmfolderpath.ps1" run directly in the PowerCLI console returns the proper VM path
All variables output the expected values
If the exact ovftool command generated in the script is run in the PowerCLI console then it will properly export the VM

Just closing the loop on this. I found a solution to my issue. My guess is that something is lost when you manipulate the list of VMs returned by the Get-VM cmdlet. In the VMs-to-OVFs script, if all of the "$VM =" lines are replaced by the single line below, then that information is retained.
$VMs = get-vm -name $vmNamingScheme
In the for loop use the ".Name" attribute to pass each individual VM object to the get-vmfolderpath script.
$vmPath = get-vm -name $VM.Name | .\get-vmfolderpath.ps1

Related

Is there a way to find every single modules which will be needed in script?

I'd like to use a kinda analyzer which will install/import all the needed modules by the script before I run it on distant machine (which could not have it) ......
any idea ?
EDIT
Here's the case :
I'm on my dev machine, I'ved already installed lots of modules of all kind (dhcp, ntfs, remoting, register, etc.)
When I finally got my script (which is a function) to work, I can't be sure of what modules are used....
What I want is to write down, in the 'begin' section, the correct imports before I send my script on remote PCs; to be sure it's gonna run perfectly, you follow ?...
Is there a kinda a third party appplication which can scan my script and give me all needed modules ?
You could do something like this to get help in finding commands used and their source/module names. It's very unpolished, just trying to give the idea.
$scriptblock = {
Write-Host "Nothing here"
$files = Get-ChildItem c:\temp
Get-ADUser someuser
Test-NetConnection www.google.com
}
# Uncomment following lines and enter the path to your script file
# $scriptFile = "Path\to\some\scriptfile"
# $scriptblock = [scriptblock]::Create((Get-Content -raw -Path $scriptFile))
$ast = $scriptblock.Ast
$commands = $ast.FindAll( { $args[0] -is [System.Management.Automation.Language.CommandAst] }, $true)
$commandText = foreach ($command in $commands) {
$command.CommandElements[0].Extent.Text
}
$commandText |
Select-Object -Unique |
Sort-Object |
Select-Object #{
Label = "CommandName"
Expression = { $_ }
},
#{
Label = "Source"
Expression = {
(Get-Command $_).Source
}
}
Output
CommandName Source
----------- ------
Get-ADUser ActiveDirectory
Get-ChildItem Microsoft.PowerShell.Management
Test-NetConnection NetTCPIP
Write-Host Microsoft.PowerShell.Utility
Yeah, you could for example test if the module exists on that particular machine by trying to import it as follows
Try {
Import-Module dbaclone -ErrorAction stop
#ErrorAction required as failing to import is not a terminating action
} Catch {
Write-Verbose -Verbose "Failed to find dbaclone module - installing"
Install-Module dbaclone -AllowClobber -Force
Write-Verbose -Verbose "Installed!"
Import-Module dbaclone
}

Batch script calling powershell script

I have a powershell script gather_objects_from_csv.ps1 which is being scheduled through cmd. I'm using this command:
powershell.exe -noexit "& 'e:\admin\gather_objects_from_csv.ps1'"
to call the powershell script, but it's throwing error for the line in the script
# create an empty hash which will hold a number of smaller hashes, of member details supplied in the csv, then piped to nw_sync_employees_8.ps1
$employees_list = #{}
# import the config.xml file containing the relevant user data, username/password etc...
$config = Import-CliXML nw-config.xml
# import some detail from the "normal" Newsweaver config file
$API_user = $config["API_USER"]
$API_user_password = $config["API_PASSWORD"]
$USE_PROXY = $config["USE_PROXY"]
$verboseMode = $config["VERBOSE_MODE"]
$account_code = $config["ACCOUNT_CODE"]
$source_file = $config["CSV_PATH"]
# check to see if the source file actually exists first
if (-Not (Test-Path $source_file)) {
# if the source file can't be found, there isn't much value in continuing
Write-Host -foregroundcolor red "Could not find specified source file. Check the path, permissions or location of source file specified: " $source_file
Exit
}
# import the source file with a specified delimiter (it is a comma as it is a CSV file which is being imported)
# pipe "|" the csv to a forEach - Object loop to iterate throught the file row by row
$HashTableData = Import-CSV $source_file -Delimiter ',' |`
# ForEach - Object - iterates over each row of the csv
ForEach-Object {
# Empty hash $memberDetails, this will be populated with the information from the csv
$memberDetails = #{}
$EmployeeID = "$($_.EmployeeID)".Trim()
$FirstName = "$($_.LegalFirstName)".Trim()
$LastName = "$($_.LegalLastName)".Trim()
$LegalNameinGeneralDisplayFormat = "$($_.LegalNameinGeneralDisplayFormat)".Trim()
$LegalNameinLocalScript = "$($_.LegalNameinLocalScript)".Trim()
#set the email address as the key for the hashtable $employees_list
#and the value of this "hash of hashes" is the hash table created above ie. memberDetails (csv information)
$employees_list.Set_Item("$($Email)", $memberDetails)
}
# pass/ pipe the hash called $employees_list to the nw_sync script with the relevant parameters
$employees_list | ./nw_sync_employees.ps1 -account_code $account_code -password $API_user_password -user $API_user -permission 'All'
exit
Error line:
nw_sync_employees.ps1: The term ./nw_sync_employees.ps1' is not
recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, of if a path was included,
verify that the path is correct and try again.
+ $employee_list | ./nw_sync_employees.ps1 -account_code $account_cod...
+ ~~~~~~~~~~~~~~~~~~~
+CategoryInfo : ObjectNotFound:
(.\nw_sync_employees.ps1:String)[], CommandNotFoundException
+FullyQualifiedErrorId: CommandNotFoundException
powershell script is calling another ps script located in the same folder. When ran through powershell terminal, the same script is not throwing any error. But when I'm trying to call it through commandline it is throwing error.
This line in the .bat works for me :
Powershell.exe "D:\myfolder\myfile.ps1"
with Administrator rights.

Powershell custom module CommandNotFoundException

When I import my module I can't access the exposed members.
Placed my module in C:\Program Files\WindowsPowerShell\Modules.
When I import my module in powershell by:
Import-Module StuiterModule -Verbose
and then enter Invoke-Reboot it gives the following error:
Invoke-Reboot : The term 'Invoke-Reboot' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or operable program. CHeck the spelling of the name, or if a path was included, verify that the path is correct and try again.
At line:1 char:1
+ Invoke-Reboot
+ ~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (Invoke-Reboot:String [], CommandNotFoundExeption
+ FullyQualifiedErrorId : CommandNotFoundException
Does anyone have an idea what I'm doing wrong?
Update
When I put -Force behind the Import Module everything works. Why is that and how can I fix this?
Code:
StuiterModule.psm1
$Public = #( Get-ChildItem -Path "$PSScriptRoot\Public\*.ps1" )
$Private = #( Get-ChildItem -Path "$PSScriptRoot\Private\*.ps1" )
#($Public + $Private) | ForEach-Object {
Try {
. $_.FullName
} Catch {
Write-Error -Message "Failed to import function $($_.FullName): $_"
}
}
Export-ModuleMember -Function $Public.BaseName
StuiterModule.psd1
#
# Module manifest for module 'StuiterModule'
#
# Generated by: StuiterSlurf
#
# Generated on: 29-5-2018
#
#{
# Script module or binary module file associated with this manifest.
RootModule = 'StuiterModule.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# ID used to uniquely identify this module
GUID = '0254592e-b712-4d70-844c-6e38cec20ee5'
# Author of this module
Author = 'StuiterSlurf'
# Copyright statement for this module
Copyright = '(c) 2018 StuiterSlurf. All rights reserved.'
# Minimum version of the Windows PowerShell engine required by this module
PowerShellVersion = '5.0'
# Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export.
FunctionsToExport = 'Invoke-Reboot', 'Start-Program', 'Update-Program'
# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = #()
# Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export.
AliasesToExport = #()
# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell.
PrivateData = #{
PSData = #{}
}
}
Public/Invoke-Reboot.ps1
# Reboot system
Function Invoke-Reboot {
[cmdletbinding()]
Param()
Write-Verbose "Starting reboot"
Restart-Computer
}

IF Statement to Verify VLAN Exists in PowerCLI Script

I am writing a PowerCLI script to automate the creation of VMs based on the data within a CSV file and I would like to know how to format an IF statement to check if the VLANs specified already exist to avoid cluttering up the screen with errors.
The section of the script dealing with the VLAN creation in its current format:
New-VM -Name $_.Name -VMHost ($esx | Get-Random) -NumCPU $_.NumCPU -Location $Folder
$list = Get-Cluster $_.Cluster | Get-VMHost
foreach ($esxhost in $list)
{ Get-VirtualSwitch -Name $switch -VMHost $esxhost |
New-VirtualPortgroup -Name "VLAN $($_.VLAN)" -VLANID $($_.VLAN)
}
Write-Host "Wait - propagating VLAN $($_.VLAN) to all hosts" -foreground yellow
Start-Sleep 10
I would like to determine a way to have the script do something like:
IF $_.VLAN exists
Write-host "$_.VLAN already present, proceeding to next step"
ELSE DO{ Get-VirtualSwitch -Name $switch -VMHost $esxhost |
New-VirtualPortgroup -Name "VLAN $($_.VLAN)" -VLANID $($_.VLAN)
}
I don't have much experience in writing these so I was hoping for some assistance on how to
Check whether the VLAN already exists in vSphere on the switch
How to format the IF/ELSE statement properly to avoid cluttering up the PowerCLI window with errors when the script is run
Thank you for any assistance you may provide
EDIT to work for vlan rather than vswitch
You could use get-virtualportgroup for this and check if the names returned contain your vlanid. This won't work for distributed switches as that's a different set of cmdlets.
$host = 'YourHost'
$vlanid = 'YourVlanId'
if ((Get-VirtualPortGroup -host $host).VLanId -contains $vlanid )
{
Write-Output 'vlan present'
}
else
{
Write-Output 'vlan missing'
#your code to create vlan here
}

Remote Powershell return value

I want to run a powershell with WinRM on serveral servers. I am using Invoke-Command with a script block.
I am reading from IIS and want to return a AppPool-Object, but I cannot access its properties - always empty.
#--imagine this code in a foreach block
$result = Invoke-Command -ComputerName $line.Servername -ScriptBlock {
Import-Module WebAdministration
$remotePoolName = Get-Item "IIS:\Sites\LeSite" #| Select-Object applictionPool
$pool = dir IIS:\AppPools | Where-Object { $_.Name -eq $remotePoolName.applicationPool }
return $pool
}
write-host $result.managedRuntimeVersion <- empty
Do I have to access it on the remote machine and return it as string ?
The problem here is, that you are referring to a property including get and set functions.
Using these functions outside of your server area results in nothing since the object is no longer in your server environment.
Using these functions inside your script block will work, because you use them on your server directly.
Greetz