Function.psm1
function split-release {
Param
(
[string]$Release
)
# Regex to match semantic versioning
if($release -notmatch '\d+[.]\d+[.]\d+')
{
Write-Error "Invalid Release Number"
}
# Split the version string into an array
$RelVersion=$release.split(".")
#{"Major"=$RelVersion[0];"Minor"=$RelVersion[1];"Patch"=$RelVersion[2]}
}
split.psm1
Import-Module .\Function.psm1
split-release
I call the function as
PS c:\ > .\split.psm1 1.2.3
It doesn't print any output or errors out.
Seems to print to the console when I test importing just that function in a psm1 file, and in a separate file import the module and then pass in "0.0.0" to split-release.
The .\ syntax indicates that the desired file is in the same directory as the caller. Is that the case with your files? Is there any additional code that may be obscuring output?
Other minor points:
Write-Host will not write to your output stream. In PS the alias to echo is Write-Output.
You can use a hash table to return these as a single object with properties.
Modified function:
function split-release {
Param
(
[string]$Release
)
# Regex to match semantic versioning
if($release -notmatch '\d+[.]\d+[.]\d+')
{
Write-Error "Invalid Release Number"
}
# Split the version string into an array
$RelVersion=$release.split(".")
#{"Major"=$RelVersion[0];"Minor"=$RelVersion[1];"Patch"=$RelVersion[2]}
}
Output:
PS C:/ > $release = "1.2.3"
PS C:/ > $result = split-release -Release $release
PS C:/ > $result.Major
1
PS C:/ > $result.Minor
2
PS C:/ > $result.Patch
3
More Info:
about_functions
I tried this finally and it appears to work.
File1.psm1
function split-release ($release) {
$RelVersion=$release.split(".")
$Relmajor=$RelVersion[0]
$Relminor=$RelVersion[1]
$Relpatch=$RelVersion[2]
write-host $Relmajor $Relminor $Relpatch
}
File2.ps1
param(
[string]$release = $(throw "Release number required as script parameter")
)
Import-Module ./File1.psm1
split-release "$release"
Finally run it as PS C:\ > ./file2.ps1
Related
I'm trying to write my first function and am having some issues. When I run the below I get no output. I feel like I'm missing something obvious but I'm not sure what.
function findModifiedFiles {
[CmdletBinding()]
param (
[string]$dir,
[int]$days
)
Process {
Write-Host "Directory: " $dir
Write-Host "Days: "$days
}
}
Output:
You ultimately need to load your function and then call the function to receive any output. Since your function is defined in a file, one way to load the function is by dot sourcing the file. Then you can simply call your function.
. .\modfilesTest.ps1
findModifiedFiles -dir c:\temp -days 7
An alternative is to not use a function at all just run the script with parameters. If we edit your file to contain the following, we can just call the script afterwards.
# modfilesTest.ps1 Contents
[CmdletBinding()]
param (
[string]$dir,
[int]$days
)
Process {
Write-Host "Directory: " $dir
Write-Host "Days: "$days
}
Now call the script with your parameters.
.\modfilesTest.ps1 -dir c:\temp -days 7
A third alternative is to just paste a function definition into your console. At that point, the function is loaded into your current scope. Then you can just call the function.
My main PowerShell code runs a function that logs to the Windows eventlog. If the level is error it uses a separate event ID which then our monitoring will pick up that exact ID and run an action. However, if I want to specify in the parameter of the main script (not the function) that this time running it use a different Event ID so it will NOT action monitoring, I don't know where to even start on that.
Is there a way to provide a switch parameter in the main script like $NoAlert which then changes the Event ID in the function?
The function of logging lives in a PowerShell module I created. I am importing the module at the beginning of the script and then calling the function during the main script body.
Here is the function:
function WriteLog-SRTProd {
Param(
[string]$logT,
[Parameter(Mandatory=$true)][string]$level,
[String]$LogFileDirT = "\\ServerA\Logs"
)
$RSLogfileT = (Get-ChildItem -Path $LogFileDirT |
sort LastWriteTime |
select -Last 1).Name
## make sure a level is correctly selected (mandatory)
if ("Error","Info","Warn" -NotContains $Level) {
throw "$($Environment) is not a valid name! Please use 'Error', 'Warn', or 'Info'"
}
if ($Level -eq "Info") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) INFO $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Information -EventId 100 -Message $logT -Category 0
}
if ($Level -eq "Warn") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) WARN $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Warning -EventId 200 -Message $logT -Category 0
}
if ($Level -eq "Error") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) ERROR $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Error -EventId 300 -Message $logT -Category 0
}
}
I'd like to run my script like this. When the $NoAlert is passed, it will send that switch to the function. Is this possible? Can I just add the switch in both places and use an if statement in the function for when the NoAlert switch is used?
PS C:\> .\Maintenance.ps1 -NoAlert
Param(
[switch]$NoAlert
)
WriteLog-SRTProd -level Error -logT "Custom Error Message"
I have created own function for logging and stored/installed as module, below is the part of my log module :
you can customize the write statements and add your code for event log. I have added 'NoAction' enum member as per your requirements.
I have used one Enum to separate the log levels
Enum Severity
{
Error = 3
Warning = 4
Informational = 6
Debug = 7
Verbose = 8
NoAction = 0 # AS PER YOUR REQUIREMENTS
}
function Write-Log()
{
[cmdletbinding()]
param
(
[Parameter(Position=0,mandatory=$true)]
[Severity] $LogLevel,
[Parameter(Position=1,mandatory=$true)]
[String] $Message
)
$TimeStamp = "$(Get-Date -format HH:mm:ss)" ;
Switch($LogLevel)
{
([Severity]::Error.ToString())
{
Write-Error "`t$TimeStamp : $Message`n" -ErrorAction Stop
break;
}
([Severity]::Warning.ToString())
{
Write-Warning "`t$TimeStamp : $Message`n" -WarningAction Continue
break;
}
([Severity]::Informational.ToString())
{
Write-Information "INROMATION:`t$TimeStamp : $Message`n" -InformationAction Continue
break;
}
([Severity]::Verbose.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
([Severity]::NoAction.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
} # END OF SWITCH
} # END OF FUNCTION
Sample Call :
Write-Log -LogLevel ([Severity]::Informational) -Message "test log message using info level"
Output :
INROMATION: 09:40:15 : test log message using info level
I have decided to just add a new parameter to both function and main script named $NoAlert. I have added an If($NoAlert){WriteLog-SRPProd -NoAlert} to the main script (messy, but its what I needed done). then in the Function, If($NoAlert){EventID 111}. so basically I am using the switch in the main script that then calls the NoAlert switch in the function. This is all done with a few added If/Else statements.
Hopefully that makes sense. Like I said its not the best answer, but I wanted to get it done and still provide an answer here in this post.
I am trying to Copy Directories - folders and Sub folders to another location using a CSV file that lists the source and destination of each directory or folder to be copied.
The Contents of the CSV are as such below:
I have referenced this thread:
https://serverfault.com/questions/399325/copying-list-of-files-through-powershell
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item "$_.Source" "$_.Destination"}
Error Received
CategoryInfo : ObjectNotFound: (#{Source=C:String) [Copy-Item], DriveNotFoundException
+ FullyQualifiedErrorId : DriveNotFound,Microsoft.PowerShell.Commands.CopyItemCommand
The other question I have is if in the CSV I want to copy to a folder that does not exists in the destination - can I use the CSV to command powershell to create the folder?
Thank you for your advice.
PowerShell will not expand the variable and access the property of the object inside the variable if you have them placed in double quotes by default. Only the '$_' is being expanded and '.source' is being tacked on to the end of the string, so from the view of the shell, your command looks something like Copy-item "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Source" "{source=C:\Users\WP\Desktop\a;Destination=C:\Users\WP\Desktop\a}.Destination", which is probably not what you mean.
Here is the syntax that should work (I also included -Recurse so that it will copy the items inside the directory as well)
Import-CSV C:\Users\WP\Desktop\a.csv | foreach{Copy-item -Path $_.Source -Destination $_.Destination -Recurse}
Note: if you want to access the properties on an object inside of double quotes, use this syntax "$($_.source)".
For a csv like this:
Source,Destination
D:\junk\test1,D:\junk\test3
D:\junk\test2,D:\junk\test4
You can use code like the following:
$csv = Import-Csv D:\junk\test.csv
$csv | ForEach-Object {
if (-not (Test-Path $_.Destination)) {
New-Item -Name $_.Destination -ItemType Directory -Force -WhatIf
}
Copy-Item $_.Source $_.Destination -Recurse -Force -WhatIf
}
Suggestions for learning more about PowerShell:
Use WhatIf to test things.
Research what each line of this code does.
Experiment with code to see what it does.
Learn and use the debugger (PowerShell ISE) to help you write better code.
Remove the WhatIf parameters from the code to have it execute for real...
If you have dozens of problems that all involve doing the same thing with each element of a list, you might want to consider getting or writing a generic CSV template expander tool, like Expand-csv. With this tool you start with a CSV file and a template, and generate a script that contains all the commands.
Sample.csv looks like this:
Source,Destination
C:\Users\WP\Desktop\a,C:\Users\WP\Desktop\c
C:\Users\WP\Desktop\b,C:\Users\WP\Desktop\d
Sample.tmplt looks like this:
Copy-Item -Path $Source -Destination $Destination -Recurse
And the command to invoke Expand-csv looks like this:
Expand-csv Sample.csv Sample.tmplt > Sample.ps1
The output file, Sample.ps1 contains one copy command for each entry in the CSV file
And here is the definition of Expand-csv:
<# This function is a table driven template tool.
It's a refinement of an earlier attempt.
It generates output from a template and
a driver table. The template file contains plain
text and embedded variables. The driver table
(in a csv file) has one column for each variable,
and one row for each expansion to be generated.
5/13/2015
#>
function Expand-csv {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)]
[string] $driver,
[Parameter(Mandatory=$true)]
[string] $template
)
Process
{
$OFS = "`r`n"
$list = Import-Csv $driver
[string]$pattern = Get-Content $template
foreach ($item in $list) {
foreach ($key in $item.psobject.properties) {
Set-variable -name $key.name -value $key.value
}
$ExecutionContext.InvokeCommand.ExpandString($pattern)
}
}
}
I am totally new to PowerShell, and trying to write a simple script to produce log file. I searched forums and could not find the answer for my question.
I found the example in the net, that I thought would be useful, and applied it to my script:
## Get current date and time. In return, you’ll get back something similar to this: Sat January 25 10:07:25 2014
$curDateTime = Get-Date
$logDate = Get-Date -format "MM-dd-yyyy HH:mm:ss"
$LogPath = "C:\Temp\Log"
$LogName = "log_file_" + $logDate + ".log"
$sFullPath = $LogPath + "\" + $LogName
<#
param(
## The path to individual location files
$Path,
## The target path of the merged file
$Destination,
## Log path
$LogPath,
## Log name
$LogName
## Full LogFile Path
## $sFullPath = $LogPath + "\" + $LogName
)
#>
Function Log-Start {
<#
.SYNOPSIS
Creates log file
.DESCRIPTION
Creates log file with path and name that is passed.
Once created, writes initial logging data
.PARAMETER LogPath
Mandatory. Path of where log is to be created. Example: C:\Windows\Temp
.PARAMETER LogName
Mandatory. Name of log file to be created. Example: Test_Script.log
.INPUTS
Parameters above
.OUTPUTS
Log file created
#>
[CmdletBinding()]
Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LogName)
Process {
## $sFullPath = $LogPath + "\" + $LogName
# Create file and start logging
New-Item -Path $LogPath -Value $LogName –ItemType File
Add-Content -Path $sFullPath -Value "***************************************************************************************************"
Add-Content -Path $sFullPath -Value "Started processing at [$([DateTime]::Now)]."
Add-Content -Path $sFullPath -Value "***************************************************************************************************"
Add-Content -Path $sFullPath -Value ""
}
}
Set-StrictMode -Version "Latest"
Log-Start
....
The question is how can I make the Log_Start function to use variables I assigned in the beginning of the script, or it is not possible with declaration of [CmdletBinding()] and function itself.
If I try to run it the way it is coded it is prompting me to enter the path and logname, I thought it should have used what I already defined. Apparently I am missing the concept. I surely can just assign the values I need right in the param declaration for the function, but I am planning to use couple of more functions like log-write and log-finish,and would not want to duplicate the same values.
What am I missing?
You defined your custom parameters at the top of your script and now you must pass them them to the function by changing
Log-Start
line to read
Log-Start $LogPath $LogName
Though you would be better off naming your parameters differently to avoid confussion. You don't really need CmdletBinding() declaration unless you plan to utilise common parameters like -Verbose or -Debug with your function so you could get rid of the following 2 lines:
[CmdletBinding()]
Param ([Parameter(Mandatory=$true)][string]$LogPath, [Parameter(Mandatory=$true)][string]$LogName)
and your script would still work.
If you want to include settings from a config file, one approach is hashtables. Your config file would look like this:
logpath=c:\somepath\
server=server.domain
Then your script would have an extra var pointing to a config file and a function to import it:
$configFile = "c:\some.config";
function GetConfig(){
$tempConfig = #{};
$configLines = cat $configFile -ErrorAction Stop;
foreach($line in $configLines){
$lineArray = $line -split "=";
$tempConfig.Add($lineArray[0].Trim(), $lineArray[1].Trim());
}
return $tempConfig;
}
$config = GetConfig
You can then assign config values to variables:
$LogPath = $conifg.Item("logpath")
$server = $conifg.Item("server")
Or use them access directly
$conifg.Item("server")
I have a script that accepts a string parameter :
script-that-takes-string-param.ps1
param(
[Parameter(Mandatory=$true, HelpMessage="path")]
[string]$path,
)
And I have another script that calls the first script :
parent-script.ps1
function CreateDir($dir) {
if (!(Test-Path $dir)) {
mkdir $dir
}
}
function CreatePath($BaseDir, $Environment, $Site, $Domain){
$path = [string]::format("{0}{1}\{2}\{3}", $BaseDir, $Environment, $Site, $Domain)
CreateDir $path
$path
}
$path = CreatePath 'c:\web\' 'qa' 'site1' 'com'
.\script-that-takes-string-param.ps1 -path $path
Running this script throws the exception :
"Cannot process argument transformation on parameter 'path'. Cannot convert value to type System.String"
Casting the parameter doesn't work :
.\script-that-takes-string-param.ps1 -path [string] $path
And casting the function result doesn't work either :
$path = [string] CreatePath 'global' 'site1'
But what is really strange is that if I run parent-script.ps1 twice from the PS command line, the 1st time it throws exceptions, but the 2nd time it executes with no errors.
My best guess would be that your
#do some other stuff with $path
writes something to the standard output, causing the function to return an array that contains said output and the path you expect. Can you send details on what you do in that bit?
Try removing "return". Output that is not saved to a variable is automatically returned. It shouldn't do any difference but it won't hurt to try.
Can you provide a full exception? Without seing the complete exception I get the feeling that the error is caused by something inside your script(ex. a function).
EDIT Your mkdir is causing the problem. When you run it, it returns an object representing the created directory(a DirectoryInfo object if I remember correctly). To fix this, try:
function CreateDir($dir) {
if (!(Test-Path $dir)) {
mkdir $dir | out-null
}
}
or combine them like:
function CreatePath($BaseDir, $Environment, $Site, $Domain){
$path = [string]::format("{0}{1}\{2}\{3}", $BaseDir, $Environment, $Site, $Domain)
if(!(Test-Path $path -PathType Container)) {
New-Item $path -ItemType Directory | Out-Null
}
$path
}