Category Archives: Uncategorized
NBuilder 6.0.1 Released

RELEASE NOTES 6.0.1

This is a bug fix release.

  • Bug Guid.Empty being incremented. Solution was to disable NBuilder’s property name for static or read-only fields. Thanks to Dominic Hemken for the PR.
  • Bug CreateListOfSize had undefined behavior when called from the static Builder and executed on multiple threads at the same time. While the future of NBulider will be to remove the static builder, it’s a defect in the current implementation. Thanks to Ovidiu Rădoi for the PR.
Testable Component Design in Rust

I consider myself an advanced beginner in Rust. There is still much I’m wrapping my head around–and I still get caught off guard by the “move” and “mutability” rules Rust enforces. However, in keeping with my personal emphasis, I’ve devoted my efforts to learning how to create automated tests in Rust. The below guidelines are not exhaustive, but represent my learning so far. Feedback is welcome!

Engineering Values

  • Code should be clean.
  • Code should be covered by automated tests.
    • Tests should be relatively easy to write.
  • Dependencies should be configurable by the components that use them (see Depedency Inversion Principle and Ports & Adapters)

Achieving These Values in Rust Component Design

These are great engineering values, but how do we achieve them practically in Rust? Here are my thoughts so far.

Required for Unit Testing

  • The component should provide a stable contract composed of traits, structs, and enums.
  • Structs exposed in the contract layer should be easy to construct in a test.
  • All types exposed in the contract layer should implement derive(Clone, Debug) so that they can be easily mocked in tests.
    • This means that types like failure::Error should be converted to something that is cloneable.

Required for Configurable Dependencies

  • The contract layer should not reference any technology or framework unless it is specifically an extension for that technology or framework.

Empathy

  • Every effort should be made to make the public api surface of your component as easy to use and understand as possible.
  • The contract layer should minimize the use of generics.
    • Obvious exceptions are Result<T> and Option<T>.
    • Concepts like PagedResult<T> that are ubiquitous can also be excepted.
    • Using type aliases to hide the generics does not qualify since the generic constraits still have to be understood and honored in a test.
    • In general this advice amounts to “generics are nice, but harder to understand than flat types. Use with care in public facing contracts.”
  • If a trait exposes a Future as a return result, it should offer a synchronous version of the same operation. This allows the client to opt-in to futures if they need them and ignore that complexity if they don’t.
    • I understand that the client can add the .wait() call to the end of a Future. My point is that an “opt-in” model is friendlier than an “opt-out” model.

Example Hypothetical Contract Surface

#[derive(Clone, Debug)]
struct Employee {
    id: String,
    type: String,
    status: String,
    first_name: String,
    last_name: String,
    address: String,
    city: String,
    birth_date: UTC,
    // snipped for brevity
}

struct PagedResponse<T> { // exposes a generic, but the reason is warranted.
    page_number: i32,
    page_size: i32,
    items: Vec<T>
}


#[derive(Debug, Clone)]
enum MyComponentError {
    Error1(String), // If the context parameter is another struct, it must also derive Clone & Debug
    Error2(i32),
};

#[derive(Clone, Debug)]
struct EmployeesQuery {
    r#type: Option<String>,
    name: String, 
    types: Vec<String>, // matches any of the specified types,
    cities: Vec<String>, // matches any of the specified cities
}

type Result<T> : Result<T, MyComponentError>; // Component level Result. Type aliasing expected here.

trait EmployeeService {
    type Employees = PagedResponse<Employee>;

    // sync version of async_get()
    fn get(id: String) -> Result<Employee>{
        async_get(id).wait();
    }

    fn async_get(id: String) -> Future<Item = Employee, Error = MyCompomentError>;

    // sync version of async_query()
    fn query(query: Option<EmployeesQuery>) -> Employees {
        async_query(id).wait()
    }

    fn async_query(query: Option<EmployeesQuery>) -> Future<Item = Transactions, Error = MyCompomentError>;

    // etc...
}
Non-Technical Engineering Quality Indicators

A PM I work with asked me the following question:
“How can someone who is not close to the engineering read the tea-leaves about the engineering quality of a given project.”

I love this question because it shows the PM cares about engineering quality despite not having engineering expertise. I’m much more accustomed to having to argue that non-technical folks should care about engineering quality. Now I have someone who does care and wants help knowing what to look for. How do I help this person? I have some starting ideas. Some of my favorite engineering manager metrics are easily adaptable for non-technical PM’s.

  1. Does the team rely on manual testing? In my opinion manual testing adds little value. It’s impossible to do a complete regression. There is no future benefit to the effort beyond the immediate release. It’s slow. If you want to remove waste from the delivery pipeline, invest in test automation.
  2. What is the defect/user story ratio in the backlog?
  3. How often are new defects discovered and added to the backlog?
  4. How does the team react to the idea of asking for a near-zero cycle time for defects? To achieve this the team would need:
    a. To have relatively few defects
    b. To receive new defects on an infrequent basis
    c. To have confidence that correcting any defect would take hours instead of days
    e. To have deep knowledge of a well-engineered system so that the exact nature of the problem can be identified quickly
    f. To have confidence that they can pass the system through their quality gates and get into production in less than an hour

The idea that you can have zero defects is sometimes shocking for both PM’s and engineers to consider. It can be an uphill battle to convince them that this is achievable in reality. If your team can’t accept this as a reality, see if they can accept is as a goal and move in this direction.

  1. Is Lead Time increasing? That could indicate that the team can not respond to work as quickly as it arrives. You will also need to know what Lead Time you want. If you want to plan your roadmap in three-month increments, your Lead Time should be between 45 and 180 days–depending on how often you update your roadmap.
  2. Is Cycle Time increasing? If so, it probably means the system is difficult to work in.
  3. Is Cycle Time close to Lead Time? If so, it could indicate a lack of planning, or an inability to work on the plan due to emergent issues.
  4. How long does it take to correct a defect in production? Ideally, this would be ~1hr from discovery.
  5. How much do we spend on support for this service through all channels? (E.g., service desk, engineering time, etc.)

This list is not exhaustive and none of these measurements would be conclusive on their own. As diagnostics however they could be quite useful to draw your attention to potential problems. Most would have to be tracked over a longer period of time to be meaningful.

If you’ve got ideas of your own, please leave them in the comments. This is an important piece of the communication between product management and engineering.

NBuilder 6.0.0 Released

Thank you to the contributors who submitted pull requests for the issues that were important to them. A summary of the changes for NBuilder 6 are as follows:

  • Breaking Change: WithConstructor
    • No longer takes an Expression<Func<T>>.
    • Takes a Func<T>.
    • Marked [Obsolete] in favor of WithFactory
    • This change was to address an issue in which the constructor expression was not being reevaluated for each item in a list.
  • Feature: @AdemCatamak Added support for IndexOf as part of the ListBuilder implementation.
var products = new Builder()
    .CreateListOfSize<Product>(10)
    .IndexOf(0, 2, 5)
    .With(x => x.Title = "A special title")
    .Build();
  • Feature: @PureKrome Added support for DateTimeKind to RandomGenerator
var result = randomGenerator.Next(DateTime.MinValue, DateTime.MaxValue, DateTimeKind.Utc);
  • Feature: Added DisablePropertyNamingFor(PropertyInfo) overload to BuilderSettings.
  • Feature: Added TheRest as an extension to the ListBuilder.
var results = new Builder()
        .CreateListOfSize<SimpleClass>(10)
        .TheFirst(2)
        .Do(row => row.String1 = "One")
        .TheRest()
        .Do(row => row.String1 = "Ten")
        .Build()
    ;
  • Bug: Last item in enum is never generated when generating property values randomly.
  • Bug: Lost strong name when porting to .NET Standard.
  • Bug: Non-deterministic behavior when calling TheLast multiple times for the same range.
A New Chapter at Microsoft

I am leaving Redacted Financial Services* in November to manage an IT team at Microsoft. I am changing the focus of my career from the day-to-day tech toward management–strategy over tactics. I’ll be bringing what I know about software engineering into the IT space as well as learning an entirely new set of disciplines.

I’ve worked at Redacted for 6 years. In that time I’ve enjoyed working with a motivated, dedicated group of Software Craftsmen. Other than getting my start writing software, it’s the best time of my professional life. I grew professionally in that time in no small part due to a manager who made room for me to explore my interests and found ways to capitalize on them for the benefit of the company. It is my goal to match his example.

One of the things I accomplished there was founding an internship program which became a feeder program into our development organization for up-and-coming developers. It had the unintended side-effect of creating a mechanism people within the company who had shown an interest in writing software could use to explore a career-change. I’ve worked with close to 30 interns. Some have stayed and worked with us. Others have gone on to companies like Visa, Google, Nordstrom, and Tableau. I’m proud to have played a part in their career development.

I took control of the hiring process for interns which expanded to include running the hiring process for our entire development organization. I learned that the largest impact I could have on my organization is through who I choose to hire. My wife works as an agency recruiter for accounting and finance professionals and with her help I learned how to work with agency recruiters to find the candidates I needed quickly. Hiring is hard and people are seldom properly trained how to do it. The end-result was that we spent less time sorting through resumes and interviewing dud-candidates. Instead, nearly every candidate we talked to was brought on-site. For the most part we were able to hire quickly with only a few cycles through the process.

A couple of years ago our DevOps initiative was going sideways. Known to be a passionate advocate for Software Craftsmanship, I was asked to ride-along with the DevOps group and make recommendations that would get us back on track. I ended up leading that group for the last year and a half. The improvements we made include tracking work in one place, identifying and eradicating root causes of common problems, clearly identifying our customers, identifying standard practices for common work, establishing a customer-centric mindset for the team, and practicing what we preach with respect to quality software. It’s a DevOps team, but we write tests for our scripts and services. In that time the stability and reliability of our production deployments increased dramatically.

In addition to being a technical leader on my team, I began managing other people. I always thought of this responsibility in servant-leadership terms. My role was to collaborate with the employee to make sure s/he is feeling challenged and growing. I learned to be free with my praise and politely direct with my critical feedback. I learned never to give critical feedback without also giving concrete examples of different behavior. I was able to coach my reports through some challenging scenarios and save them the effort of learning everything the hard way.

While I’m the one who did the work to learn these things, I was enabled by a phenomenal manager who gave me room to grow and challenge myself. He listened to my interests and made room for me to explore them–ever confident that it would pay off for the team. It did.

I was also challenged by a group of quality-focused engineers who accepted my ideas when they thought they were good, and who had the courage to speak up when they thought I was off the deep end. Some of my favorite people are my worst critics–and good friends.

Finally, I was aided by a wonderful wife with the highest emotional intelligence of any person I’ve ever encountered. I learned from her how critically important successful communication is and endeavored to apply that learning to my career. I’ve learned that I need to adapt my communication style to my audience–although putting that into practice is still a challenge!

I feel a swell of pride for having these people in my life and at the work we’ve accomplished together. To all of these people I feel a great debt of gratitude.

Thank you All.

 

* One of the interesting “perks” of working for a finance company is that some of them don’t want you to name your employer on social media. The rationale is that if you were to broadcast a stock purchase or otherwise comment on the markets it may be construed by someone else as Financial Advice which would in turn make the company potentially liable for the quality of that advice.
Stay Focused on the Goal, Not the Metrics

The goal is the thing you are trying to do.
The metric is how you are measuring your progress toward the thing you are trying to do. Metrics are only as good as their ability to measure progress toward the goal.

Don’t confuse them.

An Example

Imagine a sales team for an organization selling widgets has a goal to increase sales of a particular product line by 10%. The Sales Manager decides that the best way to achieve the goal is for the sales staff to make a certain number of cold-calls over the following months. After a few weeks, one of her sales staff is falling way behind in cold-calls. The wrinkle is that this salesman is the top biller in the department. Should the sales manager berate her top performer for not doing enough cold-calls?

Absolutely not. No manager should ever punish their reports for doing well (provided the means used are legal and ethical of course).

Punishing the salesman would send the message that billing isn’t the goal, but cold-calls are. Do you want a salesman who makes lots of cold-calls but can’t bill? Since sales staff are compensated via commission, punishing the salesman would introduce a division between his performance and his pay. In the best case, the salesman simply ignores the manager and continues to bill and get paid–benefiting the company in the process. In the worst case, the salesman leaves the company for greener pastures, depriving the company of it’s top biller.

The mistake here is that cold-calls are simply a form of measuring progress toward the goal–increased sales. Cold calls themselves are not the actual goal–they are a proxy for the goal. Further, they may not be the only possible proxy. Their value as a proxy is proportional to the relationship between cold-calls and increased sales. If a salesman is generating increased sales without cold-calls then there is either another possible metric or cold-calls are a poor metric.

It’s one thing to say that cold-calls are a proven way to generate increased sales. It’s quite another to ignore that there are other possible ways to do the same thing. It’s flat wrong to take the position that cold-calls are the only way to increase sales.

What is the appropriate response? Find out how the top biller is selling so well without cold-calls. Is the top biller doing something that no one else is doing? Is there something for the other sales staff to learn? Are there new, better metrics that can be introduced? Of course, it’s also possible that the top biller could bill even more if he did more cold-calls. Finding out will require collaboration between the salesman and the manager–but this is a process of active investigation instead of passive authoritarianism.

If the manager focuses on the metric instead of the goal, she is taking on the responsibility of having all the answers and dictating them to others. The proper approach is to adopt a learning stance toward the team’s work. If the team is doing well but the metrics aren’t being met, what can the manager learn from this? If the team meets the metrics, will they do better? If not–what good are they?

Choosing Metrics

When choosing metrics it’s important to consider that people will game the system. If you’re a software engineering manager and you make Lines of Code or Test Coverage the metric, people will write verbose code and create meaningless tests. A good metric will encourage people to game the system by focusing on the thing you want to achieve. I recently heard of an example in which the Product Owner threw out story sizing as part of their Scrum process. The only thing developers got credit for was the number of stories they completed. It didn’t matter how large or small–credit was only given when the story was completed an in production. The developers began gaming the system by reducing the story size to the smallest thing they could deliver.

Perfect.

NBuilder 5.0.0 Released: Now the .NET Standard 1.6 Support

NBuilder 5.0.0 is now available on NuGet.org.

Breaking Changes

We have dropped support for .NET 3.5. It is becoming cumbersome to support such an old framework in the build chain. We now support .NET 4.0 and above.

Exciting New Features

NBuilder is now available to .NET Core 1.1 applications via .NET Standard 1.6. This was an enormous amount of work made possible in a large part by the efforts of a contributor PureKrome. Thanks PureKrome!

NBuilder 4.0.0 Released

It’s been 5 years since a version of NBuilder was released. As happens to many of us, the original author got busy with life and was unable to spend the time brining it up to date. I volunteered to shepherd the project along, but I was also waylaid by life. However, I was recently able to spend some focused time fixing the final bugs and getting together an automated build with AppVeyor.

Given that it’s been 5 years, it’s impossible to know fully what has been changed. I did put together release notes for the things I know were changed.

My next task will be to port NBuilder to .NET Core

Release Notes for NBuilder 4.0.0

Breaking changes

1. Obsolete methods have been removed.

Any method previously marked with the Obsolete attribute has now been removed.

2. Silverlight No Longer Supported

As Silverlight is effective a dead technology, we have officially ended support for it. This will allow us to better focus on
a forthcoming release with .NET Core support.

New Features

1. Builder has a non-static implementation.

This will allow you to create customized BuilderSettings for different testing scenarios.

Old Code


var results = Builder<MyObject>.CreateListOfSize(10).Build();

New Code


var settings = new BuilderSettings() var results = new Builder(settings).CreateListOfSize<MyObject>(10).Build();

2. With and Do action now supports a signature that receives an index.

Example


var builderSettings = new BuilderSettings(); var list = new Builder(builderSettings) .CreateListOfSize<MyClassWithConstructor>(10) .All() .Do((row, index) => row.Int = index*2) .WithConstructor(() => new MyClassWithConstructor(1, 2f)) .Build();

Bug Fixes

  • The decimal separator was wrong for some cultures.
  • Random number generation of decimals was sometimes incorrect.
  • Sequences were not created in the correct order.
  • Random strings were not always generated between the expected lengths.
10 Things I Wish I Had Known Before I Switched to DevOps

1. DevOps is hard

It might not seem like it, but DevOps is hard. A few years ago I thought to myself that it can’t be that difficult since installing an individual application isn’t that difficult. I was wrong in part because…

2. Security is hard

Production is scary. I’d rather not have access when possible. On the other hand the tools that we use will definitely need access to production since it’s kind of the reason they exist. This means we have to have very tight control over who has access to the credentials that the tools run under. We work to limit our own day-to-day accounts so that their access is limited as well.

As a developer I didn’t think much about Security. I pretty much just stuffed an AD Group in a config file somewhere when I was told to and I was done. As a DevOps engineer I have had (and will continue) to learn a lot more about security and its organization even though I don’t manage security for my organization. Security impacts deployments at every level so you will have to learn about security infrastructure in order to make safe and practical recommendations to your security administration group.

3. You are not Netflix (unless you are)

Our organization got excited about DevOps tools after seeing some compelling presentations by Netflix at QCon San Francisco. Netflix has the need for highly scaled web servers which fully embrace the “cattle vs. pets” philosophy because they have millions of concurrent users of a publicly facing service.

We are not Netflix. We have 50+ internal applications with usage rates measured in the 10’s. They’re important to us–they run our business–but our problems are not the same ones Netflix faces. The tools that Netflix uses are designed to solve problems Netflix has. That doesn’t necessarily make them a good fit for our needs. We lost a lot of time and effort trying to make Netflix solutions fit our problems.

4. Windows vs. Linux matters when choosing your tools.

There are basically 5 possibilities when it comes to your server topology:

  1. Windows Only
  2. Linux Only
  3. Windows Dominant
  4. Linux Dominant
  5. Hetergeneous

If you are managing a homogeneous ecosystem then it’s imperative that you use tools that natively support that system. Don’t try to use Linux tools to manage Windows and vice versa. If you do, you’re gonna have a bad time. If you are primarily deploying to Windows you should look at tools like Octopus Deploy or Build Master. If you’re managing a Linux ecosystem look into Chef, Puppet, or even Docker.

If you’re managing a mixed ecosystem where one OS was dominant, you should still use tools designed to support the dominant system. It may be worth the effort to see if your existing tools can also manage the subdominant system. In our case it’s not worth the effort so we have instead moved toward an “appliance” model for our Linux servers. What this means is instead of managing a bunch of code to deploy RabbitMQ to Linux, we’re instead creating VM Images for the Rabbit installation which we can hydrate at will. We have far fewer resources who know how to administer Linux so this model works better for us.

5. DevOps tools are in their infancy

DevOps tools are optimized for the problems their creators were facing. There are many more problems in the DevOps space than any of the dominant tools are capable of managing on their own.

For example, Chef wants to deploy a machine. It’s not primarily concerned about applications. The Chef model is to declare the state of the machine and then let Chef decide how to bring the machine to that state. This approach optimizes for horizontally scaling hundreds or thousands of identical nodes with very few commands. Awesome!

In our organization we see the world in terms of Applications–not machines. Our whole way of thinking about deployment is different than the way Chef looks at it. This isn’t a deficiency in Chef or in the way we look at the world, but when we started using Chef we weren’t aware of how fundamental that difference in perspective would actually be.

Because Chef looks at the world in terms of nodes, it has no built-in (or even recommended) solution for artifact and version management. We had to build that. We had to build solutions for managing cookbook versions, publishing artifact and cookbook versions into targeted environments, and forwarding changes to production to antecedent environments.

If you’re using Octopus (we’re migrating from Chef to Octopus) and looking at the world in terms of applications, you will have problems when you need to spin up new environments and whole machines with many applications pre-installed. Either way, you will have to build other tools to glue the off-the-shelf tools together.

(Aside: Though I am not personally a fan of Chef, I have heard of people using Chef to deploy their infrastructure and using Octopus to deploy applications.)

6. DevOps “best practices” are in their infancy

Chef likes to advertise “use Chef however you want! We’re flexible!” Great…. except Chef is complicated and I would like some guidance on how to use it! This isn’t so much a problem with Chef though–DevOps in general is a very young field so we don’t have the wealth of shared experience from which to draw generalized lessons. To the extent that there is guidance it’s basically cribbed from Software Engineering best practices and doesn’t always apply well.

Here are some of mine:

  1. Have a canary environment that rebuilds all machines and redeploys all software on a regularly scheduled basis. Use this environment to detect problems in your deployment tool chain early.
  2. Every developer should have an individual environment of their own to test deployments.
  3. Every team should have at least one environment for testing and/or UAT.
  4. Avoid “Standard Failures.” These are errors that occur often and either do not have a known solution or have a manual workaround. Identify the root cause of errors and address them. Incorporate manual workaround solutions into your automated solutions.
  5. Where possible, embed some sort of “health check” into your applications that you can invoke to have the application check it own configuration.
  6. Identify rollback strategies for your applications.

7. Developers will have to learn infrastructure

If you come from a development background you will have to learn about security, networking, hardware, virtual hardware, etc. This is the domain you are working in now. I’m still at the beginning of this process myself but I’m starting to see the size of how much I still have to learn. For example, if you’re deploying to the cloud you’ll have to learn the inner workings of your chosen cloud infrastructure.

8. Ops will have to learn development patterns and practices.

If you come from an Ops background you will have to learn Software Engineering patterns and practices. You are graduating from someone who writes the occasional script to someone who manages code. Writing some code that only has to be run once is easy. Writing code that has to work again and again and again as well as tolerate change is much, much harder. As the number of people, environments, and machines grow software engineering skills will become more and more important.

9. Don’t automate a bad process.

Consider this: Chef doesn’t provide a built-in way to define which artifacts should be deployed to which environments. To that end we built an “application versions” cookbook which contains a list of all applications, their version, and their artifact location. In order to start work a team must:

  1. Take a branch of the application versions cookbook.
  2. Edit the versions/artifact information.
  3. Upload the cookbook to Chef
  4. commit and push the changes back to github
  5. clone the chef-repo
  6. edit the affected environment to use the new version of the application versions cookbook.
  7. commit/push chef-repo
  8. upload the edited environment to Chef.

Does that sound like a good idea to you? It doesn’t to me–but it’s necessary if you’re going to use a Chef Cookbook as a source for environment application versions. Before you go and wrap some automation around this to make it “easier,” let’s challenge the basic assumption: should we maybe just store application versions by environment elsewhere? A json file on a network share would be easier than this.

When you automate a process (even to make it “easier”) you’re pouring a certain amount of lime over it. Be careful.

10. “Infrastructure as Code!” is not always a good idea.

Code != Artifacts != Configuration. The daily work of DevOps breaks down into basically three disciplines: Code, Configuration, and Artifact management. A change to one of these should not necessitate a change to the other. That means that Code, Configuration, and Artifacts should not live together in github.

Use a Package Manager for your artifacts. If you don’t know where to look check out Artifactory. It’s a versatile artifact repository that supports many different kinds of package managers. It’s API even understands version numbers and will let you identify and retrieve the “latest” version of your artifact. Let your CI server publish artifacts to your package manager and make it the canonical source for artifact retrieval.

Configuration should not be managed like code. Configuration data is any data required by applications to run. Examples are things like dns addresses, email addresses for notifications, database connection strings, api endpoints, etc.. Configuration data is just data about environments. Unlike code it does not need to be branched. It should be stored in some central repository and accessed directly by the deployment code.

The code that you use to execute your deployments is most emphatically and in every possible way code. This means it should be tested, stored in source control, subject to your company’s chosen branching strategies, built by a CI server, etc..

The “infrastructure as code” idea is a really great idea, but it applies only to the procedure of deploying hardware and software. It does not fit well with the metadata that describes which hardware and software should be deployed. Don’t use “infrastructure as code” as an execute to push square pegs into round holes.

Asynchrony in Powershell

As part of our Octopus Deploy migration effort we are writing a powershell module that we use to automatically bootstrap the Tentacle installation into Octopus. This involves maintaining metadata about machines and environments outside of Octopus. The reason we need this capability is to adhere to the “cattle vs. pets” approach to hardware. We want to be able to destroy and recreate our machines at will and have them show up again in Octopus ready to receive deployments.

Our initial implementation cycled through one machine at a time, installing Tentacle, registering it with Octopus (with the same security certificate so that Octopus recognizes it as the same machine), then moving on to the next machine. This is fine for small environments with few machines, but not awesome for larger environments with many machines. If it takes 2m to install Tentacle and I have 30 machines, I’m waiting an hour to be able to use the environment. With this problem in mind I decided to figure out how we could parallelize the boostrapping of machines in our Powershell module.

Start-Job

Start-Job is one of a family of Powershell functions created to support asynchrony. Other related functions are Get-Job, Wait-Job, Receive-Job, and Remove-Job. In it’s most basic form, Start-Job accepts a script block as a parameter and executes it on a background thread.

# executes "dir" on a background thread.
$job = Start-Job -ScriptBlock { dir } 

The job object returned by Start-Job gives you useful information such as the job id, name, and current state. You can run Get-Job to get a list of running jobs, Wait-Job to wait on one or more jobs to complete, Receive-Job to get the output of each job, and Remove-Job to delist jobs in the current Powershell session.

Complexity

If that’s all there was to it, I wouldn’t be writing this blog post. I’d just tweet the link to the Start-Jobs msdn page and call it done. My scenario is that I need to bootstrap machines using code defined in my Powershell module, but run those commands in a background process. I also need to collate and log the output of those processes as well as report on the succes/failure of each job.

When you call Start-Job in Powershell it creates a new session in which currently loaded modules are not automatically loaded. If you have your powershell module in the $PsModulePath you’re probably okay. However, there is a difference between the version of the module I’m currently working on and testing vs. the one I have on my machine for normal use.

Start-Job has an additional parameter for a script block used to initialize the new Powershell session prior to executing your background process. The difficulty is that while you can pass arguments to the background process script block, you cannot pass arguments to the initialization script. Here’s how you make it all work.

Setup Code


# Store the working module path in an environment variable so that the new powershell session can locate the correct version of the module. # The environment variable will not persist beyond the current powershell session so we don't have to worry about poluting our machine state. $env:OctobootModulePath = (get-module Octoboot).Path $init = { # When initializing the new session, use the -Force parameter in case a different version of the module is already loaded by a profile. import-module $env:OctobootModulePath -Force } # create a parameterized script block $scriptBlock = { Param( $computerName, $environment, $roles, $userName, $password, $apiKey, ) Install-Tentacle -computer $computerName ` -environment $environment ` -roles $roles ` -userName $userName -password $password ` -apiKey $apiKey } # I like to use an -Async switch on the controlling function. Debugging issues is easier in a synchronous context than in an async context. Making the async functionality optional is a win. if ($async) { $job = Start-Job ` -ScriptBlock $scriptBlock ` -InitializationScript $init ` -Name "Install Tentacle on $($computerName)" ` -ArgumentList @( $computerName, $environment, $roles $userName, $password, $apiKey) -Debug:$debug } else { Install-Tentacle -computer $computerName ` -environment $environment ` -roles $roles ` -userName $userName -password $password ` -apiKey $apiKey }

The above code is in a loop in the controlling powershell function. After I’ve kicked off all of the jobs I’m going to execute, I just need to wait on them to finish and collect their results.

Finalization Code


if ($async) { $jobs = get-job $jobs | Wait-Job | Receive-Job $jobs | foreach { $job = $_ write-host "$($job.Id) - $($job.Name) - $($job.State)" } $jobs | remove-job }

Since each individual job is now running in parallel, bootstrapping large environments doesn’t take much longer than bootstrapping smaller ones. The end result is that hour is now reduced to a few minutes.

Previous Page · Next Page