I am building a testing framework for my current development team. One thing that I'd like to let them do is create a Powershell script to run their tests. The system is a database deploy system, so to test it they will need to potentially run some set-up code, then the deploy will be kicked off, then they will run some check code at the end.
Since the deploy takes awhile I'd like to have the framework handle that once for all of the tests. So, the basic flow is:
Run test #1 set-up
Run test #2 set-up
Run test #3 set-up
Run the deploy process
Run the code to confirm that test #1 passed
Run the code to confirm that test #2 passed
Run the code to confirm that test #3 passed
I figured that I would have the framework always call a function called "setup" (or something similar) in all of the Powershell scripts in a particular directory. If no "setup" function exists that's ok and it shouldn't error out. Then I would run the deploy and then run the other functions in the Powershell scripts.
Given a directory listing, how could I cycle through each Powershell script and run these functions?
Thanks for any guidance!
this will recurse through the given folder and execute all the setup.ps1 scripts it finds.
Get-ChildItem D:\test -Recurse | where { $_.name -eq "setup.ps1" }| foreach {
"Executing $($_.Fullname)"
Invoke-Expression "$($_.Fullname) -setup -Verbose"
}
It doesn't accept parameters though....
If you just wanted to go one folder deep this will do the job:
Get-ChildItem D:\test | where{$_.psiscontainer}|foreach {
Get-ChildItem $_.fullname | where { $_.name -eq "setup.ps1" }| foreach {
"Executing $($_.Fullname)"
Invoke-Expression "$($_.Fullname) -setup -Verbose"
}
}
It's irritated me a little that the parameters didn't work - I wonder if using the Invoke-Command could work for that. I haven't got time to try now, unless anyone else figures it out I'll have a look later.
Here's the script i used for setup.ps1
[cmdletbinding()]
Param()
function setup() {
Write-Verbose "In setup 1"
Write-Output "Done setup 1"
}
setup
HTH
Thanks to Matt's ideas I was able to come across Invoke-Expression. Ideally I would have liked to use Invoke-Command with the -filepath parameter, which supposedly defaults to running locally. However, there is a bug that requires using the -ComputerName parameter even when running locally. If you use that parameter then it requires remoting to be turned on, even when running on the local computer.
Here's the code that I used in my script:
# Run the setup functions from all of the Powershell test scripts
foreach ($testPSScript in Get-ChildItem "$testScriptDir\*.ps1") {
Invoke-Expression "$testPSScript -Setup"
}
# Do some other stuff
# Run the tests in the Powershell test scripts
foreach ($testPSScript in Get-ChildItem "$testScriptDir\*.ps1") {
Invoke-Expression "$testPSScript"
}
My test script then looked like this:
param([switch]$Setup = $false)
if ($Setup) {write-host "Setting things up"; return}
Write-Host "Running the tests"
Related
Is there away to override the PowerShell Cmdlet approach and have a PowerShell script manually process Script Arguments?
I Just want a simple example of a Powershell script that uses the classic C/perl approach of using "GetOptions" to process script argument flags manually. Instead of breaking everything up into "Command-lets", since in my case I don't care about integrating my script with the shell and returning objects for other command-let etc... Its just a one time use case where classic approach would be better than command-lets...
Here's an example:
File: script.ps1
param ($servername, $envname='Odyessy')
if ($servername -eq $null) {
$servername = read-host -Prompt "Please enter a servername"
}
write-host "If this script were really going to do something, it would do it on $servername in the $envname environment"
Example:
.\script.ps1 -servername HAL
.\script.ps1 -envname Discovery
Also See:
https://www.red-gate.com/simple-talk/sysadmin/powershell/how-to-use-parameters-in-powershell/
I have such script (here simplified):
while ($true)
{
Write-Host "Looping..."
Write-Host "Looping..." 6>> trash.txt
Start-Sleep -s 1
}
when I run it directly it works, but when I run it in the background:
Start-Job { .\sleeper.ps1 }
for a second it is seen as Running but shortly after as Failed and indeed file "trash.txt" is not created at all, so even one iteration is not executed.
What is wrong here?
I think the main issue is around the $PWD and -FilePath param, but I will list some info on Write-Host too:
Start-Job should be run with the -FilePath parameter. This is because {} is a ScriptBlock object, which is by default taken by the -ScriptBlock parameter, which you do not want. Example solution to that line: Start-Job -FilePath ./script.ps1
The $PWD or present working directory is, by default, the home directory / user profile of the current user when executed in a PowerShell job (Linux: $HOME // Windows: $Home/Documents). You can test this by executing a job simply with that variable (it may be $ENV:PWD on Windows?). Either way, it is likely not the same as the directory you are executing this in. trash.txt is being made and appended to, but it is being made in a different directory than your current directory. You will want your script to explicitly include an absolute path to the file being created, or give the script parameters that allow you to input the path at execution. Here is another StackOverflow article where a user had similar struggles, with two good solutions where one uses $args in the script and another uses the -InitializationScript parameter of Start-Job to set the $PWD: PowerShell Start-Job Working Directory
Often, Write-Host is low-priority as a selected output vs. using Write-Output / Write-Verbose / Write-Warning / Write-Error. More information about this can be found here: https://www.jsnover.com/blog/2013/12/07/write-host-considered-harmful/ - though, newer versions of PowerShell have added an information stream which I believe may make Write-Host output more accessible. More information on streams can be found with help about_Redirection
In our company we use TFS 2017 (update 1) for building and releasing our products. The release part is made up of several steps which include the execution of some Powershell scripts.
This is how I configure the PS step.
What I noticed is that the output of the powershell scripts is not written realtime while it is executing, but all together in the end of the PS task. This is very annoying in case of long running scripts as we are not able to see the live progress of the task, but we have to wait the task to finish to see the results.
I wrote some simple PS scripts to debug this problem but neither using write-host (this does not write nothing at all, even in the end of the task) nor using write-output nor with write-verbose -verbose allows me to write realtime output.
This is one example script I tried, without success.
Write-Output "Begin a lengthy process..."
$i = 0
while ($i -le 100)
{
Start-Sleep 1
Write-Output "Inner code executed"
$i += 10
}
Write-Output "Completed."
Did you ever found yourself in this situation?
Regards
I can reproduce this issue, based on my test realtime output is not supported for the PowerShell on Target Machines task.
Write-output or write-verbose -verbose just can output to console but it's not real-timed, the output only displays once the powershell script completely executed.
To display the real-time output you can use the Utility:PowerShell task instead of the Deploy:PowerShell on Target Machines task.
So, as a workaround you can deploy an agent on the target machine which you want to run the powershell script, then trigger the release using that agent running powershell script with Utility:PowerShell task.
UPDATE:
Well, find another workaround with Utility:PowerShell task:
1.Set up WinRM for target computers, refer to WinRM configuration
2.Copy the target PS script to the target machine (D:\TestShare\PStest.ps1 in below sample)
3.Create a PowerShell script to call the Powershell.exe to run the target powershell script on target machine, see below sample:
Param(
[string]$computerName = "ICTFS2015.test.com",
)
$Username = "domain\usename"
$Password = ConvertTo-SecureString "Possword" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($Username,$password)
Invoke-Command -ComputerName $computerName -Credential $cred -ScriptBlock {Invoke-Expression -Command:"powershell.exe /c 'D:\TestShare\PStest.ps1'"}
4.Add a Utility:PowerShell task to run above PowerShell script. (You can check in or run Inline Script).
I'm updating the logging function I use for all my scripts and making it compatible with the SCCM logging viewer.
Under the fields that the SCCM viewer takes is the component field, and I'll like to fill it with the main command that it's run and I'm logging. For example, if I'm logging "Data copied" and I did that with Copy-Item, I'd like that the logs says Copy-Item for that line of the log.
So far I've tested the following methods, using $$ and the history cmdlets (Get-History, Add-, etc.), but they only work with the PowerShell console, not with script execution.
The final result I'd like to get is:
I think this is what you're talking about?
$Folder = "some path"
$Cmdline = 'Remove-Item $Folder -Force -Recurse'
Invoke-Expression $Cmdline
Write-Host $Cmdline.replace("`$Folder",$Folder)
I have many scripts. After making changes, I like to run them all to see if I broke anything. I wrote a script to loop through each, running it on fresh data.
Inside my loop I'm currently running powershell.exe -command <path to script>. I don't know if that's the best way to do this, or if the two instances are totally separate from each other.
What's the preferred way to run a script in a clean instance of PowerShell? Or should I be saying "session"?
Using powershell.exe seems to be a good approach but with its pros and cons, of course.
Pros:
Each script is invoked in a separate clean session.
Even crashes do not stop the whole testing process.
Cons:
Invoking powershell.exe is somewhat slow.
Testing depends on exit codes but 0 does not always mean success.
None of the cons is mentioned is a question as a potential problem.
The demo script is below. It has been tested with PS v2 and v3. Script names
may include special characters like spaces, apostrophes, brackets, backticks,
dollars. One mentioned in comments requirement is ability to get script paths
in their code. With the proposed approach scripts can get their own path as
$MyInvocation.MyCommand.Path
# make a script list, use the full paths or explicit relative paths
$scripts = #(
'.\test1.ps1' # good name
'.\test 2.ps1' # with a space
".\test '3'.ps1" # with apostrophes
".\test [4].ps1" # with brackets
'.\test `5`.ps1' # with backticks
'.\test $6.ps1' # with a dollar
'.\test ''3'' [4] `5` $6.ps1' # all specials
)
# process each script in the list
foreach($script in $scripts) {
# make a command; mind &, ' around the path, and escaping '
$command = "& '" + $script.Replace("'", "''") + "'"
# invoke the command, i.e. the script in a separate process
powershell.exe -command $command
# check for the exit code (assuming 0 is for success)
if ($LastExitCode) {
# in this demo just write a warning
Write-Warning "Script $script failed."
}
else {
Write-Host "Script $script succeeded."
}
}
If you're on PowerShell 2.0 or higher, you can use jobs to do this. Each job runs in a separate PowerShell process e.g.:
$scripts = ".\script1.ps1", ".\script2.ps1"
$jobs = #()
foreach ($script in $scripts)
{
$jobs += Start-Job -FilePath $script
}
Wait-Job $jobs
foreach ($job in $jobs)
{
"*" * 60
"Status of '$($job.Command)' is $($job.State)"
"Script output:"
Receive-Job $job
}
Also, check out the PowerShell Community Extensions. It has a Test-Script command that can detect syntax errors in a script file. Of course, it won't catch runtime errors.
One tip for PowerShell V3 users: we (the PowerShell team) added a new API on the Runspace class called ResetRunspace(). This API resets the global variable table back to the initial state for that runspace (as well as cleaning up a few other things). What it doesn't do is clean out function definitions, types and format files or unload modules. This allows the API to be much faster. Also note that the Runspace has to have been created using an InitialSessionState object, not a RunspaceConfiguration instance. ResetRunspace() was added as part of the Workflow feature in V3 to support parallel execution efficiently in a script.
The two instances are totally separate, because they are two different processes. Generally, it is not the most efficient way to start a Powershell process for every script run. Depending on the number of scripts and how often you re-run them, it may be affecting your overall performance. If it's not, I would leave everything AS IS.
Another option would be to run in the same runspace (this is a correct word for it), but clean everything up every time. See this answer for a way to do it. Or use below extract:
$sysvars = get-variable | select -Expand name
function remove-uservars {
get-variable |
where {$sysvars -notcontains $_.name} |
remove-variable
}