Creating a PowerShell module from files - powershell

I have seen this question here but there not working for me.
I currently have taken apart a psm1 file with 200 functions and separated them into individual files.
Functions are separated into public and private functions
\\nasShare\dbasupport\Powershell\Modules\SQLdbatools\SQLdbatools\public
\\nasShare\dbasupport\Powershell\Modules\SQLdbatools\SQLdbatools\private
This is what my new SQLdbatools.psm1 file looks like
#Get public and private function definition files.
$Public = #( Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue )
$Private = #( Get-ChildItem -Path $PSScriptRoot\Private\*.ps1 -ErrorAction SilentlyContinue )
#Dot source the files
Foreach($import in #($Public + $Private))
{
Try
{
. $import.fullname
}
Catch
{
Write-Error -Message "Failed to import function $($import.fullname): $_"
}
}
Export-ModuleMember -Function $Public.Basename
But the Export-ModuleMember gives me an error that command cant be run from psm1 file
Contents of SQLdbatools.ps1xml file
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>Default</Name>
<ViewSelectedBy>
<TypeName>SQLdbatools.Question</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>48</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>12</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>5</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Owner</Label>
<Width>15</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Tags</Label>
<Width>20</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<Wrap />
<TableColumnItems>
<TableColumnItem>
<PropertyName>Title</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Answer_Count</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Score</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>$_.Owner.display_name</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>($_.Tags | Sort-Object) -Join ', '</ScriptBlock>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
<View>
<Name>Default</Name>
<ViewSelectedBy>
<TypeName>SQLdbatools.Answer</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>50</Width>
</TableColumnHeader>
<TableColumnHeader>
<Label>Owner</Label>
<Width>20</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>5</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>11</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<Wrap />
<TableColumnItems>
<TableColumnItem>
<PropertyName>Share_Link</PropertyName>
</TableColumnItem>
<TableColumnItem>
<ScriptBlock>$_.Owner.display_name</ScriptBlock>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Score</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Is_Accepted</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
contents of my SQLdbatools.psd1 file
#{
# Script module or binary module file associated with this manifest.
RootModule = 'SQLdbatools.psm1'
# Version number of this module.
ModuleVersion = '1.0.0'
# Format files (.ps1xml) to be loaded when importing this module
FormatsToProcess = 'SQLdbatools.Format.ps1xml'
# Functions to export from this module
FunctionsToExport = '*'
# Cmdlets to export from this module
CmdletsToExport = '*'
# Variables to export from this module
VariablesToExport = '*'
# Aliases to export from this module
AliasesToExport = '*'
}
I have ran the SQLdbatools.psm1 without
Export-ModuleMember -Function $Public.Basename
it will not fail but the ps1 files don't load. Is there something wrong in the code or am I not setting this Module up correctly.
Error when trying to import module
Import-Module SQLdbatools
Import-Module : The specified module 'SQLdbatools' was not loaded because no valid module file was found in any module directory.
At line:1 char:1
+ Import-Module SQLdbatools
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ResourceUnavailable: (SQLdbatools:String) [Import-Module], FileNotFoundException
+ FullyQualifiedErrorId : Modules_ModuleNotFound,Microsoft.PowerShell.Commands.ImportModuleCommand

Related

Outputting arrays with PSObject; Limitted to 4 columns

A snippet of my code:
$ipaddress = '127.0.0.1'
$port = 135,137,138,139,443,445
for($i=0; $i -lt $port.length; $i++)
{
$out = new-object psobject
$out | add-member noteproperty Host $ipaddress
$out | add-member noteproperty Port $port[$i]
$out | add-member noteproperty Isopen $isopen[$i]
$out | add-member noteproperty Desc "Desc"
$out | add-member noteproperty Notes $Notes[$i]
$out | add-member noteproperty Issue $issue[$i]
Write-Output $out
}
What I'm trying to do is print out the results of my port scanner into a nice table.
This works fine when there's 4 or less columns:
But Whenever I add more columns, even though there's space on the screen, it converts it into a list:
When I try to append "Format-Table" to it, it writes out the headers each time:
Write-Output $out | Format-Table
If I copy the line "Write-Output $out" outside the loop, it only prints out the last member. Any ideas on how to tackle this?
Thanks
As you've found, PowerShell formats your output in a table by default, but opts for list view when the objects being formatted have more than 4 visible members.
You can override this by explicitly invoking your preferred Format-* command. Simply "collect" all the output objects in an variable then explicitly pipe them to Format-Table:
$ipaddress = '127.0.0.1'
$port = 135,137,138,139,443,445
$objects = for($i=0; $i -lt $port.length; $i++)
{
$out = new-object psobject
$out | add-member noteproperty Host $ipaddress
$out | add-member noteproperty Port $port[$i]
$out | add-member noteproperty Isopen $isopen[$i]
$out | add-member noteproperty Desc "Desc"
$out | add-member noteproperty Notes $Notes[$i]
$out | add-member noteproperty Issue $issue[$i]
Write-Output $out
}
$objects |Format-Table
Unless you're running your code on PowerShell 2.0, I'd suggest using the 3.0 [pscustomobject] syntax for creating your object (and perhaps turn the whole thing into a function):
function Get-PortStatus
{
param(
[string]$IPAddress = '127.0.0.1',
[intp[]]$Port = 135,137,138,139,443,445
)
# populate $isopen, $notes, $issue etc. here ...
for($i=0; $i -lt $port.length; $i++)
{
# Write-Output is implied when the new object isn't assigned to anything
[pscustomobject]#{
Host = $ipaddress
Port = $port[$i]
IsOpen = $isopen[$i]
Desc = "Desc"
Notes = $Notes[$i]
Issue = $issue[$i]
}
}
}
Now you can do:
PS C:\Users\Gabrielius> Get-PortStatus -IPAddress '10.0.0.10' -Port 80,443 |Format-Table
You could make your own type and table view in a .format.ps1xml file, if it's worth it to you. Here's a simple example. The format is documented at About Format.ps1xml There's actually format files for all the custom objects in powershell. This is rather boilerplate. I wish there were a $numPropsToTable preference variable.
myobject.format.ps1xml:
<?xml version="1.0" encoding="utf-8" ?>
<Configuration>
<ViewDefinitions>
<View>
<Name>myobject</Name>
<ViewSelectedBy>
<TypeName>myobject</TypeName>
</ViewSelectedBy>
<TableControl>
<TableHeaders>
<TableColumnHeader>
<Width>16</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>16</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>16</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>16</Width>
</TableColumnHeader>
<TableColumnHeader>
<Width>16</Width>
</TableColumnHeader>
</TableHeaders>
<TableRowEntries>
<TableRowEntry>
<TableColumnItems>
<TableColumnItem>
<PropertyName>Name</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Address</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>City</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>State</PropertyName>
</TableColumnItem>
<TableColumnItem>
<PropertyName>Zip</PropertyName>
</TableColumnItem>
</TableColumnItems>
</TableRowEntry>
</TableRowEntries>
</TableControl>
</View>
</ViewDefinitions>
</Configuration>
update-formatdata myobject.format.ps1xml
[pscustomobject]#{name='me';address='here';city='la';state='ca';zip=11111
PSTypeName = 'MyObject'}
Name Address City State Zip
---- ------- ---- ----- ---
me here la ca 11111

CAML in Powershell with IN CLause

I'm using SP 2013 on-premises and I'm wanting to query a list for items by passing a number of IDs to the list and then returning only the Title field. This is executing in Powershell. I have the following that I am using as the ViewXml:
<View>
<ViewFields>
<FieldRef Name='Title'/>
</ViewFields>
<Query>
<Where>
<In>
<FieldRef Name='ID' />
<Values>
<Value Type='Counter'>1131</Value>
<Value Type='Counter'>478</Value>
<Value Type='Counter'>360</Value>
<Values>
</In>
</Where>
</Query>
</View>
I get the following when running $ctx.executeQuery();
Exception calling "ExecuteQuery" with "0" argument(s): "Cannot complete this action.
Please try again."
Here is the rest of the code minus the variable definitions and the bit where the client dlls are added
$pwd = Read-Host -Prompt "Enter password" -AsSecureString
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteURL)
$ctx.Credentials = New-Object System.Net.NetworkCredential($userId, $pwd)
$vFields = "<Value Type='Counter'>1131</Value><Value Type='Counter'>478</Value><Value Type='Counter'>360</Value>";
try{
$lists = $ctx.web.Lists ;
$list = $lists.GetByTitle($ListName);
$query = New-Object Microsoft.SharePoint.Client.CamlQuery;
$xmlCAML = "<View><ViewFields><FieldRef Name='Title'/></ViewFields><Query><Where><In><FieldRef Name='ID'/><Values>$vFields<Values></In></Where></Query></View>";
write-host $xmlCAML -ForegroundColor Yellow
$query.ViewXml = $xmlCAML
$listItems = $list.GetItems($query);
$ctx.load($listItems);
$ctx.executeQuery();
foreach($listItem in $listItems)
{
Write-Host "Title - " $listItem["Title"]
}
}
catch{
write-host "$($_.Exception.Message)" -foregroundcolor red
}
if you haven't already sorted this, it's just a single-keystroke fix, you've just failed to properly close the <Values>...</Values> element in your CAML. Needs to be:
<View>
<ViewFields>
<FieldRef Name='Title'/>
</ViewFields>
<Query>
<Where>
<In>
<FieldRef Name='ID' />
<Values>
<Value Type='Counter'>1131</Value>
<Value Type='Counter'>478</Value>
<Value Type='Counter'>360</Value>
</Values> <!-- <== Here :) -->
</In>
</Where>
</Query>
</View>

PowerShell XAML ListBox Add_DoubleClick

Quick one. I want to add Add_DoubleClick() to ListBox in PowerShell, but this doesn't work with implemented ListBox via XAML file... and I'm stuck
I will be very grateful for any help here...
I still have msg warning, that my post is mostly code, so I need to write something here...
# $ErrorActionPreference= 'silentlycontinue'
Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Windows.Forms, System.Drawing
$ScriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path
$AssemblyLocation = Join-Path -Path $ScriptPath -ChildPath .\themes
foreach ($Assembly in (Dir $AssemblyLocation -Filter *.dll)) {
[System.Reflection.Assembly]::LoadFrom($Assembly.fullName) | out-null
}
[xml]$xaml = #"
<Controls:MetroWindow
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
Title="ddd"
Height="500"
Width="800"
BorderThickness="0"
GlowBrush="{DynamicResource AccentColorBrush}"
ResizeMode="CanResizeWithGrip"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Colors.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/Cyan.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.AnimatedTabControl.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Accents/BaseLight.xaml" />
</ResourceDictionary.MergedDictionaries>
</Window.Resources>
<Grid>
<ListBox SelectionMode="Single" ItemsSource="{Binding}" x:Name="ListBox" HorizontalAlignment="Right" Height="100" Margin="244,144,0,0" VerticalAlignment="Top" Width="112">
<ListBoxItem Content="Coffie"></ListBoxItem>
</ListBox>
</Grid>
</Controls:MetroWindow>
"#
#Read the form
$Reader = (New-Object System.Xml.XmlNodeReader $xaml)
$Form = [Windows.Markup.XamlReader]::Load($reader)
#AutoFind all controls
$xaml.SelectNodes("//*[#*[contains(translate(name(.),'n','N'),'Name')]]") | ForEach-Object {
New-Variable -Name $_.Name -Value $Form.FindName($_.Name) -Force
}
$ListBox.Items.AddRange()
$ListBox.Add_DoubleClick({
$TextBox2.AppendText("dddd`r`n")
})
...`
I used different approach to my problem:
$smth.Add_IsMouseCapturedChanged({
$smth.SelectedIndex = -1
})

TFS 2017 - [ProjectName].setParameters.xml with powershell

I create a zip Package in my build then i deploy it to a diffrent machine i am pasing few Parameters in my parameters.xml
<?xml version="1.0" encoding="utf-8"?>
<parameters>
<parameter name="WebSiteUserName" description="Please enter the username" defaultvalue="__WebSiteUserName__" tags="">
<parameterentry kind="XmlFile" scope="\\web.config$" match="/configuration/connectionStrings/add[#key='WebSiteUserName']/#value">
</parameterentry>
</parameter>
<parameter name="WebSiteUserPassword" description="Please enter the password" defaultvalue="__UserPassword__" tags="">
<parameterentry kind="XmlFile" scope="\\web.config$" match="/configuration/connectionStrings/add[#key='WebSiteUserPassword']/#value">
</parameterentry>
</parameter>
<parameter name="WebSiteDomain" description="Domiain" defaultvalue="__Domain__" tags="">
<parameterentry kind="XmlFile" scope="\\web.config$" match="/configuration/appSettings/add[#key='WebSiteDomain']/#value">
</parameterentry>
</parameter>
</parameters>
</parameters>
Then i run a powershell script
param(
[string]$paramsFilePath
)
Write-Verbose -Verbose "Entering script Replace-SetParameters.ps1"
Write-Verbose -Verbose ("Path to SetParametersFile: {0}" -f $paramsFilePath)
# get the environment variables
$vars = Get-ChildItem -path env:*
# read in the setParameters file
$contents = Get-Content -Path $paramsFilePath
# perform a regex replacement
$newContents = "";
$contents | % {
$line = $_
if ($_ -match "__(\w+[\.\w+]*)__") {
$setting = Get-ChildItem -path env:* | ? { $_.Name -eq $Matches[1] }
while ($setting) {
Write-Verbose -Verbose ("Replacing key {0} with value from environment" -f $setting.Name)
$line = $_ -replace "__(\w+[\.\w+]*)__", $setting.Value
}
}
$newContents += $line + [Environment]::NewLine
}
Write-Verbose -Verbose "Overwriting SetParameters file with new values"
Set-Content $paramsFilePath -Value $newContents
Write-Verbose " Assigning Parameters"
Write-Verbose -Verbose "Exiting script Replace-SetParameters.ps1"
It will go through the parameters file and replace the parameters token with the enviromental variable.
In my setParamaters.xml file The WebSiteUsername only gets changed
<?xml version="1.0" encoding="utf-8"?>
<parameters>
<setParameter name="IIS Web Application Name" value="Default Web Site/SomeWebsite" />
<setParameter name="WebSiteUserName" value="username" />
<setParameter name="WebSiteUserPassword" value="__UserPassword__" />
<setParameter name="Web.config Connection String" value="SomeValueForConnection" />
</parameters>
I dont know why this is happening. Any Thoughts?
I think you want to get the password from a TFS release definition environment.
You can´t access hidden fields with
$vars = Get-ChildItem -path env:secretVariable
There is a way but that´s only works in the context of a TFS VNext build or release extension.
In your case the only possibility of access is to set the variable as a script argument like this:
-paramsFilePath $(paramsFilePath) -password $(secretVariable)
In your script add the parameter like
param(
[string]$paramsFilePath,
[string]$password
)

Trying to get log4net working with PowerShell (with a log4net config file in the mix)

I have been struggling with getting log4net working with PowerShell. I have the following PowerShell code that pulls in log4net with a configuration file and then attempts to make a simple log file entry but which errors out.
Clear-History
Clear-Host
#
Write-Host "BEGIN: Importing module psm_logger.psm1"
Import-Module "C:\Users\Administrator\Desktop\ps\IIS\psm_logger.psm1" -Force
Write-Host "BEGIN: log4net configuration definition"
$log = New-Logger -Configuration "C:\Users\Administrator\Desktop\ps\IIS\logging\log4net.config" -Dll "C:\Users\Administrator\Desktop\ps\IIS\logging\log4net.dll"
Write-Host "END: log4net configuration definition"
Write-Host "BEGIN: log4net configuration DEBUG definition"
#$log.DebugFormat("Logger configuration file is : '{0}'", (Resolve-Path "C:\Users\Administrator\Desktop\ps\IIS\logging\log4net.config"))
Write-Host "END: log4net DEBUG configuration definition"
#======================
$ThisHostname = $env:COMPUTERNAME
#======================
Write-Host "BEGIN: Module/Snapin import/addition"
Write-Host ""
$log.Info("BEGIN: Module/Snapin import/addition")
#======================
The above referenced psm_logger.psm1 file contains:
function New-Logger
{
#Write-Host "BEGIN: New-Logger"
<#
.SYNOPSIS
This function creates a log4net logger instance already configured
.OUTPUTS
The log4net logger instance ready to be used
#>
[CmdletBinding()]
Param
(
[string]
# Path of the configuration file of log4net
$Configuration,
[Alias("Dll")]
[string]
# Log4net dll path
$log4netDllPath
)
Write-Verbose "[New-Logger] Logger initialization"
$log4netDllPath = Resolve-Path $log4netDllPath -ErrorAction SilentlyContinue -ErrorVariable Err
if ($Err)
{
throw "Log4net library cannot be found on the path $log4netDllPath"
}
else
{
Write-Verbose "[New-Logger] Log4net dll path is : '$log4netDllPath'"
[void][Reflection.Assembly]::LoadFrom($log4netDllPath) | Out-Null
# Log4net configuration loading
$log4netConfigFilePath = Resolve-Path $Configuration -ErrorAction SilentlyContinue -ErrorVariable Err
if ($Err)
{
throw "Log4Net configuration file $Configuration cannot be found"
}
else
{
Write-Verbose "[New-Logger] Log4net configuration file is '$log4netConfigFilePath' "
$FileInfo = New-Object System.IO.FileInfo($log4netConfigFilePath)
[log4net.Config.XmlConfigurator]::Configure($FileInfo)
$script:MyCommonLogger = [log4net.LogManager]::GetLogger("root")
Write-Verbose "[New-Logger] Logger is configured"
return $MyCommonLogger
}
}
}
The configuration file for log4net looks like:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" **requirePermission="false"**/>
</configSections>
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
<param name="File" value="C:\Users\Administrator\Desktop\ps\IIS\LOGS\LogTest2.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
</root>
</log4net>
</configuration>
When run the output AND error produced is:
BEGIN: Importing module psm_logger.psm1
BEGIN: log4net configuration definition
END: log4net configuration definition
BEGIN: log4net configuration DEBUG definition
END: log4net DEBUG configuration definition
BEGIN: Module/Snapin import/addition
Method invocation failed because [System.Object[]] doesn't contain a method named 'Info'.
At C:\Users\Administrator\Desktop\ps\IIS\IIS_localLogs.ps1:18 char:10
+ $log.Info <<<< ("BEGIN: Module/Snapin import/addition")
+ CategoryInfo : InvalidOperation: (Info:String) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
Any help would be greatly appreciated!
(FYI: I originally got this code from a best practices blog entry here: http://blog.octo.com/en/powershell-v2-my-best-practices/ which I found very helpful)
Found the solution...
The config file was corrupted by the presence of some asterisks towards the top. Removing those fixed the issue. Thank you to anybody who may have started to look at this and I hope this helps somebody else in the future.
The configuration file now looks like:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" requirePermission="false"/>
</configSections>
<log4net>
<appender name="LogFileAppender" type="log4net.Appender.FileAppender">
<param name="File" value="C:\Users\Administrator\Desktop\ps\IIS\LOGS\LogTest2.txt" />
<param name="AppendToFile" value="true" />
<layout type="log4net.Layout.PatternLayout">
<param name="Header" value="[Header]\r\n" />
<param name="Footer" value="[Footer]\r\n" />
<param name="ConversionPattern" value="%d [%t] %-5p %c %m%n" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
</root>
</log4net>
</configuration>
New-Logger is such a useful cmdlet, it's worth updating for newer versions of Powershell. psm_logger.psm1 currently gives a System.Void error under PS V3 and later. Changing the assembly load to
[Reflection.Assembly]::LoadFrom($log4netDllPath) | Out-Null
without the [Void] sorts out the problem.